[SCM] Samba Shared Repository - branch master updated

Douglas Bagnall dbagnall at samba.org
Thu Jul 5 04:52:13 UTC 2018


The branch, master has been updated
       via  9a7e9e5 autobuild: Fix random-sleep.sh invocation in autobuild.py
       via  41d86e5 samba_tool_showrepl_pull_summary_all_good is flapping
       via  64e3502 samba-tool drs showrepl test: turn subprocess error into failure
       via  2860bd0 netcmd: Use dbcheck to fix DB problems introduced by restore itself
       via  62948a3 tests: Add new tests for backup-rename command
       via  20568e0 selftest: Add dedicated RENAMEDC testenv for 'backup rename'
       via  6681f90 netcmd: Extend 'backup restore' command to handle renamed domains
       via  ab65647 netcmd: Add 'samba-tool domain backup rename' command
       via  cd727c9 tests: Tweak the backup online tests so they're generic
       via  850bba4 drs_utils: Always set the GET_TGT flag for clone renames
       via  634a72d join: Add more framework for renaming a domain
      from  734ea27 uid_wrapper: Be strict when checking __attribute__ features

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 9a7e9e527f5fa50883137f7acc33ba72627aae16
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Jul 5 11:09:50 2018 +1200

    autobuild: Fix random-sleep.sh invocation in autobuild.py
    
    The scripts were not running with the correct path and this causes sn-devel to hit
    a very high load as many of the compile jobs start at once.
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    
    Autobuild-User(master): Douglas Bagnall <dbagnall at samba.org>
    Autobuild-Date(master): Thu Jul  5 06:51:26 CEST 2018 on sn-devel-144

commit 41d86e5f2bf537f72f8a543492fcafe38c936657
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Jul 5 13:49:23 2018 +1200

    samba_tool_showrepl_pull_summary_all_good is flapping
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 64e350219826355bb2d4f3c7b42ea4f29439526e
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Jul 5 11:01:58 2018 +1200

    samba-tool drs showrepl test: turn subprocess error into failure
    
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 2860bd0777eff1bff85f2000adf8fe4d788aad56
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Wed Jul 4 13:23:59 2018 +1200

    netcmd: Use dbcheck to fix DB problems introduced by restore itself
    
    As part of the restore process, we remove all the old DCs from the DB.
    However, this introduces some dbcheck errors - there are some DN
    attributes and one-way links that reference the deleted objects that
    need fixing up. To resolve this, we can run dbcheck as part of the
    restore process. This problem affects both renames and plain restores.
    
    The dbcheck.sh test didn't spot this problem because it fixes this type
    of DB error first, before it checks the DB.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 62948a3099cbb6073f3e5e454eaaaadd134e3082
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Tue Jul 3 13:55:53 2018 +1200

    tests: Add new tests for backup-rename command
    
    Extend the existing 'backup online' tests to also test the domain
    rename case. This mostly involves some extra assertions that the
    restored DB has been modified appropriatelt (i.e. domain NetBIOS
    name is updated, etc).
    
    I've also added an extra test case that creates a few objects and
    links and specifically asserts that they get renamed appropriately.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 20568e00e68631cf7b514024ba2926fd8c73c8e1
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Mon Jun 11 11:02:11 2018 +1200

    selftest: Add dedicated RENAMEDC testenv for 'backup rename'
    
    Add a new testenv that's similar to the existing restoredc, except we
    use 'backup rename' to rename the domain as we back it up.
    
    Restoring this backup then proves that a valid DC can be started from a
    renamed backup.
    
    Run the same sub-set of RESTOREDC tests to prove that the new testenv is
    sound.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 6681f904aaf0cd9d757572a4f41376bd2e1c01fd
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Mon Jun 11 11:18:09 2018 +1200

    netcmd: Extend 'backup restore' command to handle renamed domains
    
    When restoring a renamed domain backup, we need to register the new
    realm's DNS zone. We do this in the restore step because we don't know
    the new server's IP/hostname in the backup step.
    
    Because we may have removed the old realm's DNS entries in the rename
    step, the remove_dc() code may fail to find the expected DNS entries for
    the DC's domain (the DCs' dnsHostname still maps to the old DNS realm).
    We just needed to adjust remove_dns_references() as it was getting a
    slightly different error code.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit ab65647a8b146832eea6e98ee9e1b891f9c36788
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Wed Jun 13 12:22:19 2018 +1200

    netcmd: Add 'samba-tool domain backup rename' command
    
    Add a new command that takes a clone of the domain's DB, and renames the
    domain as well. (We rename the domain during the clone because it's
    easier to implement - the DRS code handles most of the renaming for us,
    as it applies the received replication chunks).
    
    The new option is similar to an online backup, except we also do the
    following:
    - use the new DCCloneAndRenameContext code to clone the DB
    - run dbcheck to fix up any residual old DNs (mostly objectCategory
      references)
    - rename the domain's netBIOSName
    - add dnsRoot objects for the new DNS realm
    - by default, remove the old realm's DNS objects (optional)
    - add an extra backupRename marker to the backed-up DB. In the restore
      code, if the backup was renamed, then we need to register the new
      domain's DNS zone at that point (we only know the new DC's host IP
      at the restore stage).
    
    Note that the backup will contain the old DC entries that still use the
    old dnsHostname, but these DC entries will all be removed during the
    restore, and a new DC will be added with the correct dnsHostname.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit cd727c95a288995686cf4300b9a069ccf76b8e46
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Tue Jul 3 13:43:29 2018 +1200

    tests: Tweak the backup online tests so they're generic
    
    Update backup-online tests to be more generic. We can then re-use the
    common framework for other types of backups (offline, rename), and just
    change what's specific to those particular cases.
    
    This change includes asserting the restored backup's domain/realm are
    correct, which we weren't doing previously but makes sense.
    
    The new 'return samdb' is for convenience, so that child classes can
    easily extend the checks we run over the restored DB.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 850bba4d329d062c8a79824c0a9987fa51340e2b
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Wed Jun 13 14:09:06 2018 +1200

    drs_utils: Always set the GET_TGT flag for clone renames
    
    The DCCloneAndRenameContext replication was a little inefficient, in
    that it would essentially replicate the entire DB twice. This was due to
    resolving the link targets - it finds a target object it doesn't know
    about, so retries the entire replication again with the GET_TGT flag set
    this time.
    
    Normally, the repl_meta_data code will use the target object's GUID,
    however, it can't do this for cross-partition links (if it hasn't
    replicated the target partition yet). The repl_md code can normally
    detect that the link is a cross-parition link by checking the base-DN,
    however, this doesn't work in the DCCloneAndRenameContext case because
    we have renamed the base-DN.
    
    This is not a big deal - it just means extra work. However, because the
    domains being backed up could potentially be quite large, it probably
    makes sense to just always set the GET_TGT in the rename case and skip
    this extra work.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 634a72df24e74bce07d2481fb86b0ce4b7ebaa38
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Mon Jun 11 16:50:28 2018 +1200

    join: Add more framework for renaming a domain
    
    Add a DCCloneContext subclass which will rename the DB objects as they
    get cloned. This uses the drs_ReplicateRenamer class added to drs_utils
    in an earlier patch. Where the drs_Replicate object currently gets
    created has been split out into a simple new function, which we can then
    override in the rename case.
    
    The other important difference is overriding the provision step, so that
    we use the new domain-DN/realm when setting up the initial SAM DB (and
    smb.conf, secrets.ldb, etc).
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

-----------------------------------------------------------------------

Summary of changes:
 docs-xml/manpages/samba-tool.8.xml                 |   5 +
 python/samba/drs_utils.py                          |   9 +-
 python/samba/join.py                               | 100 ++++++-
 python/samba/netcmd/domain_backup.py               | 308 ++++++++++++++++++++-
 python/samba/provision/sambadns.py                 |   5 +-
 python/samba/remove_dc.py                          |   3 +-
 python/samba/tests/domain_backup.py                | 179 ++++++++++--
 script/autobuild.py                                |  24 +-
 selftest/flapping.d/samba_tool_drs_showrepl        |   1 +
 selftest/target/Samba.pm                           |   1 +
 selftest/target/Samba4.pm                          |  45 +++
 source4/selftest/tests.py                          |  11 +-
 .../torture/drs/python/samba_tool_drs_showrepl.py  |  36 ++-
 13 files changed, 667 insertions(+), 60 deletions(-)
 create mode 100644 selftest/flapping.d/samba_tool_drs_showrepl


Changeset truncated at 500 lines:

diff --git a/docs-xml/manpages/samba-tool.8.xml b/docs-xml/manpages/samba-tool.8.xml
index b8038bc..7f000e9 100644
--- a/docs-xml/manpages/samba-tool.8.xml
+++ b/docs-xml/manpages/samba-tool.8.xml
@@ -302,6 +302,11 @@
 </refsect3>
 
 <refsect3>
+	<title>domain backup rename</title>
+	<para>Copy a running DC's DB to backup file, renaming the domain in the process.</para>
+</refsect3>
+
+<refsect3>
 	<title>domain backup restore</title>
 	<para>Restore the domain's DB from a backup-file.</para>
 </refsect3>
diff --git a/python/samba/drs_utils.py b/python/samba/drs_utils.py
index db83267..66f4750 100644
--- a/python/samba/drs_utils.py
+++ b/python/samba/drs_utils.py
@@ -200,6 +200,7 @@ class drs_Replicate(object):
         if invocation_id == misc.GUID("00000000-0000-0000-0000-000000000000"):
             raise RuntimeError("Must not set GUID 00000000-0000-0000-0000-000000000000 as invocation_id")
         self.replication_state = self.net.replicate_init(self.samdb, lp, self.drs, invocation_id)
+        self.more_flags = 0
 
     def _should_retry_with_get_tgt(self, error_code, req):
 
@@ -228,7 +229,7 @@ class drs_Replicate(object):
         # setup for a GetNCChanges call
         if self.supported_extensions & drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V10:
             req = drsuapi.DsGetNCChangesRequest10()
-            req.more_flags = more_flags
+            req.more_flags = (more_flags | self.more_flags)
             req_level = 10
         else:
             req_level = 8
@@ -367,6 +368,12 @@ class drs_ReplicateRenamer(drs_Replicate):
         self.old_base_dn = old_base_dn
         self.new_base_dn = new_base_dn
 
+        # because we're renaming the DNs, we know we're going to have trouble
+        # resolving link targets. Normally we'd get to the end of replication
+        # only to find we need to retry the whole replication with the GET_TGT
+        # flag set. Always setting the GET_TGT flag avoids this extra work.
+        self.more_flags = drsuapi.DRSUAPI_DRS_GET_TGT
+
     def rename_dn(self, dn_str):
         '''Uses string substitution to replace the base DN'''
         return re.sub('%s$' % self.old_base_dn, self.new_base_dn, dn_str)
diff --git a/python/samba/join.py b/python/samba/join.py
index 39c9a3a..3b648f5 100644
--- a/python/samba/join.py
+++ b/python/samba/join.py
@@ -43,6 +43,9 @@ import logging
 import talloc
 import random
 import time
+import re
+import os
+import tempfile
 
 class DCJoinException(Exception):
 
@@ -904,6 +907,12 @@ class DCJoinContext(object):
                                  dns_backend=ctx.dns_backend, adminpass=ctx.adminpass)
         print("Provision OK for domain %s" % ctx.names.dnsdomain)
 
+    def create_replicator(ctx, repl_creds, binding_options):
+        '''Creates a new DRS object for managing replications'''
+        return drs_utils.drs_Replicate(
+                "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
+                ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
+
     def join_replicate(ctx):
         """Replicate the SAM."""
 
@@ -929,9 +938,8 @@ class DCJoinContext(object):
             binding_options = "seal"
             if ctx.lp.log_level() >= 9:
                 binding_options += ",print"
-            repl = drs_utils.drs_Replicate(
-                "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
-                ctx.lp, repl_creds, ctx.local_samdb, ctx.invocation_id)
+
+            repl = ctx.create_replicator(repl_creds, binding_options)
 
             repl.replicate(ctx.schema_dn, source_dsa_invocation_id,
                     destination_dsa_guid, schema=True, rodc=ctx.RODC,
@@ -1614,3 +1622,89 @@ class DCCloneContext(DCJoinContext):
         ctx.join_provision()
         ctx.join_replicate()
         ctx.join_finalise()
+
+
+# Used to create a renamed backup of a DC. Renaming the domain means that the
+# cloned/backup DC can be started without interfering with the production DC.
+class DCCloneAndRenameContext(DCCloneContext):
+    """Clones a remote DC, renaming the domain along the way."""
+
+    def __init__(ctx, new_base_dn, new_domain_name, new_realm, logger=None,
+                 server=None, creds=None, lp=None, targetdir=None, domain=None,
+                 dns_backend=None, include_secrets=True):
+        super(DCCloneAndRenameContext, ctx).__init__(logger, server, creds, lp,
+                                                     targetdir=targetdir,
+                                                     domain=domain,
+                                                     dns_backend=dns_backend,
+                                                     include_secrets=include_secrets)
+        # store the new DN (etc) that we want the cloned DB to use
+        ctx.new_base_dn = new_base_dn
+        ctx.new_domain_name = new_domain_name
+        ctx.new_realm = new_realm
+
+    def create_replicator(ctx, repl_creds, binding_options):
+        """Creates a new DRS object for managing replications"""
+
+        # We want to rename all the domain objects, and the simplest way to do
+        # this is during replication. This is because the base DN of the top-
+        # level replicated object will flow through to all the objects below it
+        binding_str = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
+        return drs_utils.drs_ReplicateRenamer(binding_str, ctx.lp, repl_creds,
+                                              ctx.local_samdb,
+                                              ctx.invocation_id,
+                                              ctx.base_dn, ctx.new_base_dn)
+
+    def create_non_global_lp(ctx, global_lp):
+        '''Creates a non-global LoadParm based on the global LP's settings'''
+
+        # the samba code shares a global LoadParm by default. Here we create a
+        # new LoadParm that retains the global settings, but any changes we
+        # make to it won't automatically affect the rest of the samba code.
+        # The easiest way to do this is to dump the global settings to a
+        # temporary smb.conf file, and then load the temp file into a new
+        # non-global LoadParm
+        fd, tmp_file = tempfile.mkstemp()
+        global_lp.dump(False, tmp_file)
+        local_lp = samba.param.LoadParm(filename_for_non_global_lp=tmp_file)
+        os.remove(tmp_file)
+        return local_lp
+
+    def rename_dn(ctx, dn_str):
+        '''Uses string substitution to replace the base DN'''
+        old_base_dn = ctx.base_dn
+        return re.sub('%s$' % old_base_dn, ctx.new_base_dn, dn_str)
+
+    # we want to override the normal DCCloneContext's join_provision() so that
+    # use the new domain DNs during the provision. We do this because:
+    # - it sets up smb.conf/secrets.ldb with the new realm/workgroup values
+    # - it sets up a default SAM DB that uses the new Schema DNs (without which
+    #   we couldn't apply the renamed DRS objects during replication)
+    def join_provision(ctx):
+        """Provision the local (renamed) SAM."""
+
+        print("Provisioning the new (renamed) domain...")
+
+        # the provision() calls make_smbconf() which uses lp.dump()/lp.load()
+        # to create a new smb.conf. By default, it uses the global LoadParm to
+        # do this, and so it would overwrite the realm/domain values globally.
+        # We still need the global LoadParm to retain the old domain's details,
+        # so we can connect to (and clone) the existing DC.
+        # So, copy the global settings into a non-global LoadParm, which we can
+        # then pass into provision(). This generates a new smb.conf correctly,
+        # without overwriting the global realm/domain values just yet.
+        non_global_lp = ctx.create_non_global_lp(ctx.lp)
+
+        # do the provision with the new/renamed domain DN values
+        presult = provision(ctx.logger, system_session(),
+                targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
+                realm=ctx.new_realm, lp=non_global_lp,
+                rootdn=ctx.rename_dn(ctx.root_dn), domaindn=ctx.new_base_dn,
+                schemadn=ctx.rename_dn(ctx.schema_dn),
+                configdn=ctx.rename_dn(ctx.config_dn),
+                domain=ctx.new_domain_name, domainsid=ctx.domsid,
+                serverrole="active directory domain controller",
+                dns_backend=ctx.dns_backend)
+
+        print("Provision OK for renamed domain DN %s" % presult.domaindn)
+        ctx.local_samdb = presult.samdb
+        ctx.paths = presult.paths
diff --git a/python/samba/netcmd/domain_backup.py b/python/samba/netcmd/domain_backup.py
index c706c3c..c156046 100644
--- a/python/samba/netcmd/domain_backup.py
+++ b/python/samba/netcmd/domain_backup.py
@@ -29,7 +29,7 @@ import ldb
 from samba import smb
 from samba.ntacls import backup_online, backup_restore
 from samba.auth import system_session
-from samba.join import DCJoinContext, join_clone
+from samba.join import DCJoinContext, join_clone, DCCloneAndRenameContext
 from samba.dcerpc.security import dom_sid
 from samba.netcmd import Option, CommandError
 from samba.dcerpc import misc
@@ -39,7 +39,12 @@ from samba.provision import make_smbconf
 from samba.upgradehelpers import update_krbtgt_account_password
 from samba.remove_dc import remove_dc
 from samba.provision import secretsdb_self_join
-
+from samba.dbchecker import dbcheck
+import re
+from samba.provision import guess_names, determine_host_ip, determine_host_ip6
+from samba.provision.sambadns import (fill_dns_data_partitions,
+                                      get_dnsadmins_sid,
+                                      get_domainguid)
 
 
 # work out a SID (based on a free RID) to use when the domain gets restored.
@@ -233,6 +238,10 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
         Option("--backup-file", help="Path to backup file", type=str),
         Option("--targetdir", help="Path to write to", type=str),
         Option("--newservername", help="Name for new server", type=str),
+        Option("--host-ip", type="string", metavar="IPADDRESS",
+               help="set IPv4 ipaddress"),
+        Option("--host-ip6", type="string", metavar="IP6ADDRESS",
+               help="set IPv6 ipaddress"),
     ]
 
     takes_optiongroups = {
@@ -240,8 +249,66 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
         "credopts": options.CredentialsOptions,
     }
 
+    def register_dns_zone(self, logger, samdb, lp, ntdsguid, host_ip,
+                          host_ip6):
+        '''
+        Registers the new realm's DNS objects when a renamed domain backup
+        is restored.
+        '''
+        names = guess_names(lp)
+        domaindn = names.domaindn
+        forestdn = samdb.get_root_basedn().get_linearized()
+        dnsdomain = names.dnsdomain.lower()
+        dnsforest = dnsdomain
+        hostname = names.netbiosname.lower()
+        domainsid = dom_sid(samdb.get_domain_sid())
+        dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
+        domainguid = get_domainguid(samdb, domaindn)
+
+        # work out the IP address to use for the new DC's DNS records
+        host_ip = determine_host_ip(logger, lp, host_ip)
+        host_ip6 = determine_host_ip6(logger, lp, host_ip6)
+
+        if host_ip is None and host_ip6 is None:
+            raise CommandError('Please specify a host-ip for the new server')
+
+        logger.info("DNS realm was renamed to %s" % dnsdomain)
+        logger.info("Populating DNS partitions for new realm...")
+
+        # Add the DNS objects for the new realm (note: the backup clone already
+        # has the root server objects, so don't add them again)
+        fill_dns_data_partitions(samdb, domainsid, names.sitename, domaindn,
+                                 forestdn, dnsdomain, dnsforest, hostname,
+                                 host_ip, host_ip6, domainguid, ntdsguid,
+                                 dnsadmins_sid, add_root=False)
+
+    def fix_old_dc_references(self, samdb):
+        '''Fixes attributes that reference the old/removed DCs'''
+
+        # we just want to fix up DB problems here that were introduced by us
+        # removing the old DCs. We restrict what we fix up so that the restored
+        # DB matches the backed-up DB as close as possible. (There may be other
+        # DB issues inherited from the backed-up DC, but it's not our place to
+        # silently try to fix them here).
+        samdb.transaction_start()
+        chk = dbcheck(samdb, quiet=True, fix=True, yes=False,
+                      in_transaction=True)
+
+        # fix up stale references to the old DC
+        setattr(chk, 'fix_all_old_dn_string_component_mismatch', 'ALL')
+        attrs = ['lastKnownParent', 'interSiteTopologyGenerator']
+
+        # fix-up stale one-way links that point to the old DC
+        setattr(chk, 'remove_plausible_deleted_DN_links', 'ALL')
+        attrs += ['msDS-NC-Replica-Locations']
+
+        cross_ncs_ctrl = 'search_options:1:2'
+        controls = ['show_deleted:1', cross_ncs_ctrl]
+        chk.check_database(controls=controls, attrs=attrs)
+        samdb.transaction_commit()
+
     def run(self, sambaopts=None, credopts=None, backup_file=None,
-            targetdir=None, newservername=None):
+            targetdir=None, newservername=None, host_ip=None, host_ip6=None):
         if not (backup_file and os.path.exists(backup_file)):
             raise CommandError('Backup file not found.')
         if targetdir is None:
@@ -312,7 +379,8 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
         # Get the SID saved by the backup process and create account
         res = samdb.search(base=ldb.Dn(samdb, "@SAMBA_DSDB"),
                            scope=ldb.SCOPE_BASE,
-                           attrs=['sidForRestore'])
+                           attrs=['sidForRestore', 'backupRename'])
+        is_rename = True if 'backupRename' in res[0] else False
         sid = res[0].get('sidForRestore')[0]
         logger.info('Creating account with SID: ' + str(sid))
         ctx.join_add_objects(specified_sid=dom_sid(sid))
@@ -325,6 +393,13 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
                                                 "dsServiceName")
         samdb.modify(m)
 
+        # if we renamed the backed-up domain, then we need to add the DNS
+        # objects for the new realm (we do this in the restore, now that we
+        # know the new DC's IP address)
+        if is_rename:
+            self.register_dns_zone(logger, samdb, lp, ctx.ntds_guid,
+                                   host_ip, host_ip6)
+
         secrets_path = os.path.join(private_dir, 'secrets.ldb')
         secrets_ldb = Ldb(secrets_path, session_info=system_session(), lp=lp)
         secretsdb_self_join(secrets_ldb, domain=ctx.domain_name,
@@ -374,8 +449,8 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
                                                  ldb.FLAG_MOD_REPLACE,
                                                  "repsFrom")
             msg["repsTo"] = ldb.MessageElement([],
-                                                 ldb.FLAG_MOD_REPLACE,
-                                                 "repsTo")
+                                               ldb.FLAG_MOD_REPLACE,
+                                               "repsTo")
             samdb.modify(msg)
 
         # Update the krbtgt passwords twice, ensuring no tickets from
@@ -392,6 +467,10 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
         backup_restore(sysvol_tar, dest_sysvol_dir, samdb, smbconf)
         os.remove(sysvol_tar)
 
+        # fix up any stale links to the old DCs we just removed
+        logger.info("Fixing up any remaining references to the old DCs...")
+        self.fix_old_dc_references(samdb)
+
         # Remove DB markers added by the backup process
         m = ldb.Message()
         m.dn = ldb.Dn(samdb, "@SAMBA_DSDB")
@@ -399,6 +478,9 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
                                              "backupDate")
         m["sidForRestore"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
                                                 "sidForRestore")
+        if is_rename:
+            m["backupRename"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
+                                                   "backupRename")
         samdb.modify(m)
 
         logger.info("Backup file successfully restored to %s" % targetdir)
@@ -406,7 +488,221 @@ class cmd_domain_backup_restore(cmd_fsmo_seize):
                     "starting samba.")
 
 
+class cmd_domain_backup_rename(samba.netcmd.Command):
+    '''Copy a running DC's DB to backup file, renaming the domain in the process.
+
+    Where <new-domain> is the new domain's NetBIOS name, and <new-dnsrealm> is
+    the new domain's realm in DNS form.
+
+    This is similar to 'samba-tool backup online' in that it clones the DB of a
+    running DC. However, this option also renames all the domain entries in the
+    DB. Renaming the domain makes it possible to restore and start a new Samba
+    DC without it interfering with the existing Samba domain. In other words,
+    you could use this option to clone your production samba domain and restore
+    it to a separate pre-production environment that won't overlap or interfere
+    with the existing production Samba domain.
+
+    Note that:
+    - it's recommended to run 'samba-tool dbcheck' before taking a backup-file
+      and fix any errors it reports.
+    - all the domain's secrets are included in the backup file.
+    - although the DB contents can be untarred and examined manually, you need
+      to run 'samba-tool domain backup restore' before you can start a Samba DC
+      from the backup file.
+    - GPO and sysvol information will still refer to the old realm and will
+      need to be updated manually.
+    - if you specify 'keep-dns-realm', then the DNS records will need updating
+      in order to work (they will still refer to the old DC's IP instead of the
+      new DC's address).
+    - we recommend that you only use this option if you know what you're doing.
+    '''
+
+    synopsis = ("%prog <new-domain> <new-dnsrealm> --server=<DC-to-backup> "
+                "--targetdir=<output-dir>")
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+    }
+
+    takes_options = [
+        Option("--server", help="The DC to backup", type=str),
+        Option("--targetdir", help="Directory to write the backup file",
+               type=str),
+        Option("--keep-dns-realm", action="store_true", default=False,
+               help="Retain the DNS entries for the old realm in the backup"),
+       ]
+
+    takes_args = ["new_domain_name", "new_dns_realm"]
+
+    def update_dns_root(self, logger, samdb, old_realm, delete_old_dns):
+        '''Updates dnsRoot for the partition objects to reflect the rename'''
+
+        # lookup the crossRef objects that hold the old realm's dnsRoot
+        partitions_dn = samdb.get_partitions_dn()
+        res = samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL,
+                           attrs=["dnsRoot"],
+                           expression='(&(objectClass=crossRef)(dnsRoot=*))')
+        new_realm = samdb.domain_dns_name()
+
+        # go through and add the new realm
+        for res_msg in res:
+            # dnsRoot can be multi-valued, so only look for the old realm
+            for dns_root in res_msg["dnsRoot"]:
+                dn = res_msg.dn
+                if old_realm in dns_root:
+                    new_dns_root = re.sub('%s$' % old_realm, new_realm,
+                                          dns_root)
+                    logger.info("Adding %s dnsRoot to %s" % (new_dns_root, dn))
+
+                    m = ldb.Message()
+                    m.dn = dn
+                    m["dnsRoot"] = ldb.MessageElement(new_dns_root,
+                                                      ldb.FLAG_MOD_ADD,
+                                                      "dnsRoot")
+                    samdb.modify(m)
+
+                    # optionally remove the dnsRoot for the old realm
+                    if delete_old_dns:
+                        logger.info("Removing %s dnsRoot from %s" % (dns_root,
+                                                                     dn))
+                        m["dnsRoot"] = ldb.MessageElement(dns_root,
+                                                          ldb.FLAG_MOD_DELETE,
+                                                          "dnsRoot")
+                        samdb.modify(m)
+
+    # Updates the CN=<domain>,CN=Partitions,CN=Configuration,... object to
+    # reflect the domain rename
+    def rename_domain_partition(self, logger, samdb, new_netbios_name):
+        '''Renames the domain parition object and updates its nETBIOSName'''
+
+        # lookup the crossRef object that holds the nETBIOSName (nCName has
+        # already been updated by this point, but the netBIOS hasn't)
+        base_dn = samdb.get_default_basedn()
+        nc_name = ldb.binary_encode(str(base_dn))
+        partitions_dn = samdb.get_partitions_dn()
+        res = samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL,
+                           attrs=["nETBIOSName"],
+                           expression='ncName=%s' % nc_name)
+
+        logger.info("Changing backup domain's NetBIOS name to %s" %
+                    new_netbios_name)
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["nETBIOSName"] = ldb.MessageElement(new_netbios_name,
+                                              ldb.FLAG_MOD_REPLACE,
+                                              "nETBIOSName")
+        samdb.modify(m)
+
+        # renames the object itself to reflect the change in domain
+        new_dn = "CN=%s,%s" % (new_netbios_name, partitions_dn)
+        logger.info("Renaming %s --> %s" % (res[0].dn, new_dn))
+        samdb.rename(res[0].dn, new_dn, controls=['relax:0'])
+
+    def delete_old_dns_zones(self, logger, samdb, old_realm):
+        # remove the top-level DNS entries for the old realm
+        basedn = samdb.get_default_basedn()
+        dn = "DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones,%s" % (old_realm, basedn)
+        logger.info("Deleting old DNS zone %s" % dn)
+        samdb.delete(dn, ["tree_delete:1"])
+
+        forestdn = samdb.get_root_basedn().get_linearized()
+        dn = "DC=_msdcs.%s,CN=MicrosoftDNS,DC=ForestDnsZones,%s" % (old_realm,
+                                                                    forestdn)
+        logger.info("Deleting old DNS zone %s" % dn)
+        samdb.delete(dn, ["tree_delete:1"])
+
+    def fix_old_dn_attributes(self, samdb):
+        '''Fixes attributes (i.e. objectCategory) that still use the old DN'''
+
+        samdb.transaction_start()
+        # Just fix any mismatches in DN detected (leave any other errors)
+        chk = dbcheck(samdb, quiet=True, fix=True, yes=False,
+                      in_transaction=True)
+        # fix up incorrect objectCategory/etc attributes
+        setattr(chk, 'fix_all_old_dn_string_component_mismatch', 'ALL')
+        cross_ncs_ctrl = 'search_options:1:2'
+        controls = ['show_deleted:1', cross_ncs_ctrl]
+        chk.check_database(controls=controls)
+        samdb.transaction_commit()
+
+    def run(self, new_domain_name, new_dns_realm, sambaopts=None,
+            credopts=None, server=None, targetdir=None, keep_dns_realm=False):
+        logger = self.get_logger()
+        logger.setLevel(logging.INFO)
+
+        # Make sure we have all the required args.
+        check_online_backup_args(logger, credopts, server, targetdir)
+        delete_old_dns = not keep_dns_realm
+
+        new_dns_realm = new_dns_realm.lower()
+        new_domain_name = new_domain_name.upper()
+
+        new_base_dn = samba.dn_from_dns_name(new_dns_realm)
+        logger.info("New realm for backed up domain: %s" % new_dns_realm)
+        logger.info("New base DN for backed up domain: %s" % new_base_dn)
+        logger.info("New domain NetBIOS name: %s" % new_domain_name)
+
+        tmpdir = tempfile.mkdtemp(dir=targetdir)


-- 
Samba Shared Repository



More information about the samba-cvs mailing list