2018-05-01 02:10:31 +03:00
# Unix SMB/CIFS implementation.
# Copyright (C) Andrew Bartlett <abartlet@samba.org>
#
# 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 os
import shutil
import tempfile
2021-05-24 05:58:40 +03:00
from samba . tests import BlackboxTestCase , BlackboxProcessError
2018-05-01 02:10:31 +03:00
from samba . netcmd import CommandError
2021-03-18 00:52:52 +03:00
from samba . param import LoadParm
2022-09-23 03:32:25 +03:00
from samba import safe_tarfile as tarfile
2018-05-01 02:10:31 +03:00
2018-11-22 06:56:22 +03:00
# The backup tests require that a completely clean LoadParm object gets used
# for the restore. Otherwise the same global LP gets re-used, and the LP
# settings can bleed from one test case to another.
# To do this, these tests should use check_output(), which executes the command
# in a separate process (as opposed to runcmd(), runsubcmd()).
# So although this is a samba-tool test, we don't inherit from SambaToolCmdTest
# so that we never inadvertently use .runcmd() by accident.
class DomainBackupOfflineCmp ( BlackboxTestCase ) :
2018-05-01 02:10:31 +03:00
2021-03-18 00:52:52 +03:00
def test_domain_backup_offline_nested_tdb ( self ) :
self . nested_testcase ( ' tdb ' )
def test_domain_backup_offline_nested_mdb ( self ) :
self . nested_testcase ( ' mdb ' )
def nested_testcase ( self , backend ) :
self . prov_dir = self . provision ( backend )
self . extract_dir = None
src = os . path . join ( self . prov_dir , " private " )
dst = os . path . join ( self . prov_dir , " state " , " private " )
# Move private directory inside state directory
shutil . move ( src , dst )
smbconf = os . path . join ( self . prov_dir , " etc " , " smb.conf " )
# Update the conf file
lp = LoadParm ( filename_for_non_global_lp = smbconf )
lp . set ( " private dir " , dst )
lp . dump ( False , smbconf )
backup_file = self . backup ( self . prov_dir )
# Ensure each file is only present once in the tar file
tf = tarfile . open ( backup_file )
names = tf . getnames ( )
self . assertEqual ( len ( names ) , len ( set ( names ) ) )
2021-05-24 05:58:40 +03:00
def test_domain_backup_offline_join_restore_tdb ( self ) :
self . join_restore_testcase ( ' tdb ' )
def test_domain_backup_offline_join_restore_mdb ( self ) :
self . join_restore_testcase ( ' mdb ' )
def join_restore_testcase ( self , backend ) :
self . prov_dir = self . join ( backend )
self . extract_dir = None
try :
backup_file = self . backup ( self . prov_dir )
except BlackboxProcessError as e :
self . fail ( e )
self . extract_dir = self . restore ( backup_file )
2021-03-16 06:13:05 +03:00
def test_domain_backup_offline_hard_link_tdb ( self ) :
self . hard_link_testcase ( ' tdb ' )
def test_domain_backup_offline_hard_link_mdb ( self ) :
self . hard_link_testcase ( ' mdb ' )
def hard_link_testcase ( self , backend ) :
self . prov_dir = self . provision ( backend )
self . extract_dir = None
# Create hard links in the private and state directories
os . link ( os . path . join ( self . prov_dir , " private " , " krb5.conf " ) ,
os . path . join ( self . prov_dir , " state " , " krb5.conf " ) )
backup_file = self . backup ( self . prov_dir )
# Extract the backup
self . extract_dir = tempfile . mkdtemp ( dir = self . tempdir )
tf = tarfile . open ( backup_file )
tf . extractall ( self . extract_dir )
# Ensure that the hard link in the private directory was backed up,
# while the one in the state directory was not.
self . assertTrue ( os . path . exists ( os . path . join ( self . extract_dir ,
" private " , " krb5.conf " ) ) )
self . assertFalse ( os . path . exists ( os . path . join ( self . extract_dir ,
" statedir " , " krb5.conf " ) ) )
2018-05-01 02:10:31 +03:00
def test_domain_backup_offline_untar_tdb ( self ) :
self . untar_testcase ( ' tdb ' )
2021-03-16 12:46:02 +03:00
def test_domain_backup_offline_untar_mdb ( self ) :
2018-05-01 02:10:31 +03:00
self . untar_testcase ( ' mdb ' )
def test_domain_backup_offline_restore_tdb ( self ) :
self . restore_testcase ( ' tdb ' )
2021-03-16 12:46:02 +03:00
def test_domain_backup_offline_restore_mdb ( self ) :
2018-05-01 02:10:31 +03:00
self . restore_testcase ( ' mdb ' )
def restore_testcase ( self , backend ) :
2021-03-16 06:13:05 +03:00
self . prov_dir = self . provision ( backend )
self . extract_dir = None
backup_file = self . backup ( self . prov_dir )
2018-05-01 02:10:31 +03:00
2021-05-24 05:58:40 +03:00
self . extract_dir = self . restore ( backup_file )
2018-05-01 02:10:31 +03:00
# attrs that are altered by the restore process
ignore_attrs = [ " servicePrincipalName " , " lastLogonTimestamp " ,
2021-05-26 04:40:30 +03:00
" rIDAllocationPool " , " rIDAvailablePool " , " rIDUsedPool " ,
2018-05-01 02:10:31 +03:00
" localPolicyFlags " , " operatingSystem " , " displayName " ,
" dnsRecord " , " dNSTombstoned " ,
" msDS-NC-Replica-Locations " , " msDS-HasInstantiatedNCs " ,
2024-03-01 02:14:58 +03:00
" interSiteTopologyGenerator " , " msKds-DomainID " ]
2018-05-01 02:10:31 +03:00
filter_arg = " --filter= " + " , " . join ( ignore_attrs )
args = [ " --two " , filter_arg ]
2021-03-16 06:13:05 +03:00
self . ldapcmp ( self . prov_dir , self . extract_dir , args )
2018-05-01 02:10:31 +03:00
def untar_testcase ( self , backend ) :
2021-03-16 06:13:05 +03:00
self . prov_dir = self . provision ( backend )
self . extract_dir = None
backup_file = self . backup ( self . prov_dir )
2018-05-01 02:10:31 +03:00
2021-03-16 06:13:05 +03:00
self . extract_dir = tempfile . mkdtemp ( dir = self . tempdir )
2018-05-01 02:10:31 +03:00
tf = tarfile . open ( backup_file )
2021-03-16 06:13:05 +03:00
tf . extractall ( self . extract_dir )
2018-05-01 02:10:31 +03:00
2021-03-16 06:13:05 +03:00
self . ldapcmp ( self . prov_dir , self . extract_dir )
2018-05-01 02:10:31 +03:00
2023-02-23 05:54:37 +03:00
def ldapcmp ( self , prov_dir , ex_dir , args = None ) :
if args is None :
args = [ ]
2018-05-01 02:10:31 +03:00
sam_fn = os . path . join ( " private " , " sam.ldb " )
url1 = " tdb:// " + os . path . join ( os . path . realpath ( prov_dir ) , sam_fn )
url2 = " tdb:// " + os . path . join ( os . path . realpath ( ex_dir ) , sam_fn )
# Compare the restored sam.ldb with the old one
for partition in [ " domain " , " configuration " , " schema " ,
" dnsdomain " , " dnsforest " ] :
cmd = " samba-tool ldapcmp " + " " . join ( [ url1 , url2 , partition ] + args )
self . check_output ( cmd )
# Test the "samba-tool domain backup" command with ldapcmp
2021-03-16 06:13:05 +03:00
def provision ( self , backend ) :
target = tempfile . mkdtemp ( dir = self . tempdir )
2018-05-01 02:10:31 +03:00
# Provision domain. Use fake ACLs and store xattrs in tdbs so that
# NTACL backup will work inside the testenv.
# host-name option must be given because if this test runs on a
# system with a very long hostname, it will be shortened in certain
# circumstances, causing the ldapcmp to fail.
prov_cmd = " samba-tool domain provision " + \
" --domain FOO --realm foo.example.com " + \
2021-03-16 06:13:05 +03:00
" --targetdir {target} " + \
2018-05-01 02:10:31 +03:00
" --backend-store {backend} " + \
" --host-name OLDSERVER " + \
2021-05-27 02:48:52 +03:00
" --option= \" vfs objects=dfs_samba4 acl_xattr fake_acls xattr_tdb \" "
2021-03-16 06:13:05 +03:00
prov_cmd = prov_cmd . format ( target = target , backend = backend )
2018-05-01 02:10:31 +03:00
self . check_output ( prov_cmd )
2021-03-16 06:13:05 +03:00
return target
2021-05-24 05:58:40 +03:00
def join ( self , backend ) :
target = tempfile . mkdtemp ( dir = self . tempdir )
2021-06-18 11:46:51 +03:00
new_dc_name = " offlinebackupdc "
2021-05-24 05:58:40 +03:00
join_cmd = " samba-tool domain join {domain} DC " + \
" --server {server} " + \
" --realm {realm} " + \
" --username {username} % {password} " + \
" --targetdir {target} " + \
" --backend-store {backend} " + \
2021-06-18 11:46:51 +03:00
" --option= ' netbios name = {new_dc_name} ' " + \
2021-05-24 05:58:40 +03:00
" --option= \" vfs objects=dfs_samba4 acl_xattr fake_acls xattr_tdb \" "
join_cmd = join_cmd . format ( server = os . environ [ " DC_SERVER " ] ,
domain = os . environ [ " DOMAIN " ] ,
realm = os . environ [ " REALM " ] ,
username = os . environ [ " USERNAME " ] ,
password = os . environ [ " PASSWORD " ] ,
target = target ,
2021-06-18 11:46:51 +03:00
backend = backend ,
new_dc_name = new_dc_name )
2021-05-24 05:58:40 +03:00
self . check_output ( join_cmd )
2021-06-18 11:46:51 +03:00
demote_cmd = " samba-tool domain demote " + \
" --server {server} " + \
" --username {username} % {password} " + \
" --remove-other-dead-server= {new_dc_name} "
demote_cmd = demote_cmd . format ( server = os . environ [ " DC_SERVER " ] ,
username = os . environ [ " USERNAME " ] ,
password = os . environ [ " PASSWORD " ] ,
new_dc_name = new_dc_name )
self . check_output ( demote_cmd )
2021-05-24 05:58:40 +03:00
return target
2021-03-16 06:13:05 +03:00
def backup ( self , prov_dir ) :
2018-05-01 02:10:31 +03:00
# Run the backup and check we got one backup tar file
cmd = ( " samba-tool domain backup offline --targetdir= {prov_dir} "
2021-04-14 12:44:51 +03:00
" --configfile= {prov_dir} /etc/smb.conf " ) . format ( prov_dir = prov_dir )
2018-05-01 02:10:31 +03:00
self . check_output ( cmd )
tar_files = [ fn for fn in os . listdir ( prov_dir )
if fn . startswith ( " samba-backup- " ) and
fn . endswith ( " .tar.bz2 " ) ]
if len ( tar_files ) != 1 :
raise CommandError ( " expected domain backup to create one tar " +
2018-09-21 04:22:56 +03:00
" file but got {0} " . format ( len ( tar_files ) ) )
2018-05-01 02:10:31 +03:00
backup_file = os . path . join ( prov_dir , tar_files [ 0 ] )
2021-03-16 06:13:05 +03:00
return backup_file
2021-05-24 05:58:40 +03:00
def restore ( self , backup_file ) :
# Restore from a backup file
extract_dir = tempfile . mkdtemp ( dir = self . tempdir )
cmd = ( " samba-tool domain backup restore --backup-file= {f} "
" --targetdir= {d} "
" --newservername=NEWSERVER " ) . format ( f = backup_file ,
d = extract_dir )
self . check_output ( cmd )
return extract_dir
2021-03-16 06:13:05 +03:00
def tearDown ( self ) :
# Remove temporary directories
shutil . rmtree ( self . prov_dir )
if self . extract_dir :
shutil . rmtree ( self . extract_dir )