netcmd: domain: move demote command to domain/demote.py
authorRob van der Linde <rob@catalyst.net.nz>
Thu, 30 Mar 2023 23:42:24 +0000 (12:42 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Fri, 31 Mar 2023 07:25:32 +0000 (07:25 +0000)
Signed-off-by: Rob van der Linde <rob@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/netcmd/domain/__init__.py
python/samba/netcmd/domain/demote.py [new file with mode: 0644]

index a20c190fe1cc91a21c3111f1e86c5acbf789e81f..f92cf78ef29ec87bd8ec1ef7842306462a44ba89 100644 (file)
@@ -41,13 +41,11 @@ import samba.ntacls
 from samba.join import join_RODC, join_DC
 from samba.auth import system_session
 from samba.samdb import SamDB, get_default_backend_store
-from samba.dcerpc import drsuapi
 from samba.dcerpc import drsblobs
 from samba.dcerpc import lsa
 from samba.dcerpc import netlogon
 from samba.dcerpc import security
 from samba.dcerpc import nbt
-from samba.dcerpc import misc
 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
 from samba.netcmd import (
     Command,
@@ -61,8 +59,7 @@ from samba.netcmd.common import (NEVER_TIMESTAMP,
                                  timestamp_to_mins,
                                  timestamp_to_days)
 from samba.samba3 import param as s3param
-from samba.drs_utils import drsuapi_connect
-from samba import remove_dc, string_to_byte_array
+from samba import string_to_byte_array
 from samba.auth_util import system_session_unix
 from samba.net_s3 import Net as s3_Net
 from samba.param import default_path
@@ -101,13 +98,13 @@ from samba.provision.common import (
 from samba.netcmd.pso import cmd_domain_passwordsettings_pso
 
 from samba.trust_utils import CreateTrustedDomainRelax
-from samba import dsdb
 
 from .backup import cmd_domain_backup
 from .classicupgrade import cmd_domain_classicupgrade
 from .common import (common_join_options, common_ntvfs_options,
                      common_provision_join_options)
 from .dcpromo import cmd_domain_dcpromo
+from .demote import cmd_domain_demote
 
 string_version_to_constant = {
     "2000": DS_DOMAIN_FUNCTION_2000,
@@ -702,304 +699,6 @@ class cmd_domain_leave(Command):
         s3_net.leave(keep_account)
 
 
-class cmd_domain_demote(Command):
-    """Demote ourselves from the role of Domain Controller."""
-
-    synopsis = "%prog [options]"
-
-    takes_options = [
-        Option("--server", help="writable DC to write demotion changes on", type=str),
-        Option("-H", "--URL", help="LDB URL for database or target server", type=str,
-               metavar="URL", dest="H"),
-        Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
-               "to remove ALL references to (rather than this DC)", type=str),
-        Option("-q", "--quiet", help="Be quiet", action="store_true"),
-        Option("-v", "--verbose", help="Be verbose", action="store_true"),
-    ]
-
-    takes_optiongroups = {
-        "sambaopts": options.SambaOptions,
-        "credopts": options.CredentialsOptions,
-        "versionopts": options.VersionOptions,
-    }
-
-    def run(self, sambaopts=None, credopts=None,
-            versionopts=None, server=None,
-            remove_other_dead_server=None, H=None,
-            verbose=False, quiet=False):
-        lp = sambaopts.get_loadparm()
-        creds = credopts.get_credentials(lp)
-        net = Net(creds, lp, server=credopts.ipaddress)
-
-        logger = self.get_logger(verbose=verbose, quiet=quiet)
-
-        if remove_other_dead_server is not None:
-            if server is not None:
-                samdb = SamDB(url="ldap://%s" % server,
-                              session_info=system_session(),
-                              credentials=creds, lp=lp)
-            else:
-                samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
-            try:
-                remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
-            except remove_dc.DemoteException as err:
-                raise CommandError("Demote failed: %s" % err)
-            return
-
-        netbios_name = lp.get("netbios name")
-        samdb = SamDB(url=H, 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 last 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("Deactivating inbound replication\n")
-
-        nmsg = ldb.Message()
-        nmsg.dn = msg[0].dn
-
-        if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
-            dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
-            nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
-            samdb.modify(nmsg)
-
-            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()):
-                nc = drsuapi.DsReplicaObjectIdentifier()
-                nc.dn = str(part)
-
-                req1 = drsuapi.DsReplicaSyncRequest1()
-                req1.naming_context = nc
-                req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
-                req1.source_dsa_guid = misc.GUID(ntds_guid)
-
-                try:
-                    drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
-                except RuntimeError as e1:
-                    (werr, string) = e1.args
-                    if werr == werror.WERR_DS_DRA_NO_REPLICA:
-                        pass
-                    else:
-                        self.errf.write(
-                            "Error while replicating out last local changes from '%s' for demotion, "
-                            "re-enabling inbound replication\n" % part)
-                        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 partition '%s'" % str(part), string)
-        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.domain_dn()),
-                                      expression="(&(objectClass=user)(sAMAccountName=%s$))" %
-                                      netbios_name.upper(),
-                                      attrs=["userAccountControl"])
-            dc_dn = res[0].dn
-            uac = int(str(res[0]["userAccountControl"]))
-
-        except Exception as e:
-            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
-                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):
-            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
-                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 |
-                 UF_PARTIAL_SECRETS_ACCOUNT)
-        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 as e:
-            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
-                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()
-        dc_name = res[0].dn.get_rdn_value()
-        rdn = "CN=%s" % dc_name
-
-        # Let's move to the Computer container
-        i = 0
-        newrdn = str(rdn)
-
-        computer_dn = remote_samdb.get_wellknown_dn(
-            remote_samdb.get_default_basedn(),
-            dsdb.DS_GUID_COMPUTERS_CONTAINER)
-        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:
-                if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
-                    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 as e:
-            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
-                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:
-            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 RuntimeError as e3:
-            (werr, string) = e3.args
-            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
-                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")
-            remote_samdb.modify(msg)
-            remote_samdb.rename(newdn, dc_dn)
-            if werr == werror.WERR_DS_DRA_NO_REPLICA:
-                raise CommandError("The DC %s is not present on (already "
-                                   "removed from) the remote server: %s" %
-                                   (server_dsa_dn, e3))
-            else:
-                raise CommandError("Error while sending a removeDsServer "
-                                   "of %s: %s" %
-                                   (server_dsa_dn, e3))
-
-        remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
-
-        # These are objects under the computer account that should be deleted
-        for s in ("CN=Enterprise,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 as l:
-                pass
-
-        # get dns host name for target server to demote, remove dns references
-        remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
-                                        ignore_no_name=True)
-
-        self.errf.write("Demote successful\n")
-
-
 class cmd_domain_level(Command):
     """Raise domain and forest function levels."""
 
diff --git a/python/samba/netcmd/domain/demote.py b/python/samba/netcmd/domain/demote.py
new file mode 100644 (file)
index 0000000..f3868a2
--- /dev/null
@@ -0,0 +1,339 @@
+# domain management - domain demote
+#
+# 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-2015
+# 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 ldb
+import samba.getopt as options
+from samba import dsdb, remove_dc, werror
+from samba.auth import system_session
+from samba.dcerpc import drsuapi, misc
+from samba.drs_utils import drsuapi_connect
+from samba.dsdb import (
+    DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
+    DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
+    UF_PARTIAL_SECRETS_ACCOUNT,
+    UF_SERVER_TRUST_ACCOUNT,
+    UF_TRUSTED_FOR_DELEGATION,
+    UF_WORKSTATION_TRUST_ACCOUNT
+)
+from samba.net import Net
+from samba.netcmd import Command, CommandError, Option
+from samba.samdb import SamDB
+
+
+class cmd_domain_demote(Command):
+    """Demote ourselves from the role of Domain Controller."""
+
+    synopsis = "%prog [options]"
+
+    takes_options = [
+        Option("--server", help="writable DC to write demotion changes on", type=str),
+        Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+               metavar="URL", dest="H"),
+        Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
+               "to remove ALL references to (rather than this DC)", type=str),
+        Option("-q", "--quiet", help="Be quiet", action="store_true"),
+        Option("-v", "--verbose", help="Be verbose", action="store_true"),
+    ]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+    }
+
+    def run(self, sambaopts=None, credopts=None,
+            versionopts=None, server=None,
+            remove_other_dead_server=None, H=None,
+            verbose=False, quiet=False):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp)
+        net = Net(creds, lp, server=credopts.ipaddress)
+
+        logger = self.get_logger(verbose=verbose, quiet=quiet)
+
+        if remove_other_dead_server is not None:
+            if server is not None:
+                samdb = SamDB(url="ldap://%s" % server,
+                              session_info=system_session(),
+                              credentials=creds, lp=lp)
+            else:
+                samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
+            try:
+                remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
+            except remove_dc.DemoteException as err:
+                raise CommandError("Demote failed: %s" % err)
+            return
+
+        netbios_name = lp.get("netbios name")
+        samdb = SamDB(url=H, 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 last 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("Deactivating inbound replication\n")
+
+        nmsg = ldb.Message()
+        nmsg.dn = msg[0].dn
+
+        if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+            dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+            nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+            samdb.modify(nmsg)
+
+            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()):
+                nc = drsuapi.DsReplicaObjectIdentifier()
+                nc.dn = str(part)
+
+                req1 = drsuapi.DsReplicaSyncRequest1()
+                req1.naming_context = nc
+                req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
+                req1.source_dsa_guid = misc.GUID(ntds_guid)
+
+                try:
+                    drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
+                except RuntimeError as e1:
+                    (werr, string) = e1.args
+                    if werr == werror.WERR_DS_DRA_NO_REPLICA:
+                        pass
+                    else:
+                        self.errf.write(
+                            "Error while replicating out last local changes from '%s' for demotion, "
+                            "re-enabling inbound replication\n" % part)
+                        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 partition '%s'" % str(part), string)
+        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.domain_dn()),
+                                      expression="(&(objectClass=user)(sAMAccountName=%s$))" %
+                                      netbios_name.upper(),
+                                      attrs=["userAccountControl"])
+            dc_dn = res[0].dn
+            uac = int(str(res[0]["userAccountControl"]))
+
+        except Exception as e:
+            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+                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):
+            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+                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 |
+                 UF_PARTIAL_SECRETS_ACCOUNT)
+        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 as e:
+            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+                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()
+        dc_name = res[0].dn.get_rdn_value()
+        rdn = "CN=%s" % dc_name
+
+        # Let's move to the Computer container
+        i = 0
+        newrdn = str(rdn)
+
+        computer_dn = remote_samdb.get_wellknown_dn(
+            remote_samdb.get_default_basedn(),
+            dsdb.DS_GUID_COMPUTERS_CONTAINER)
+        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:
+                if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+                    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 as e:
+            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+                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:
+            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 RuntimeError as e3:
+            (werr, string) = e3.args
+            if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+                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")
+            remote_samdb.modify(msg)
+            remote_samdb.rename(newdn, dc_dn)
+            if werr == werror.WERR_DS_DRA_NO_REPLICA:
+                raise CommandError("The DC %s is not present on (already "
+                                   "removed from) the remote server: %s" %
+                                   (server_dsa_dn, e3))
+            else:
+                raise CommandError("Error while sending a removeDsServer "
+                                   "of %s: %s" %
+                                   (server_dsa_dn, e3))
+
+        remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
+
+        # These are objects under the computer account that should be deleted
+        for s in ("CN=Enterprise,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 as l:
+                pass
+
+        # get dns host name for target server to demote, remove dns references
+        remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
+                                        ignore_no_name=True)
+
+        self.errf.write("Demote successful\n")