[PATCH: Domain backup samba-tool command]
Aaron Haslett
aaronhaslett at catalyst.net.nz
Fri Mar 23 04:59:42 UTC 2018
The exists shell script for backing up a domain doesn't lock things
properly while doing the backup and could end up with a corrupt backup
or cause a lockup. Here's a new python script that actually works,
along with tests and required fixes.
-------------- next part --------------
From 479ddb468518e7845fc0b9ac97e0d32984d4d606 Mon Sep 17 00:00:00 2001
From: Aaron <aaronhaslett at catalyst.net.nz>
Date: Fri, 23 Mar 2018 15:10:49 +1300
Subject: [PATCH 1/7] netcmd: added domain backup tool and test
Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
---
python/samba/netcmd/domain.py | 2 +
python/samba/netcmd/domain_backup.py | 157 +++++++++++++++++++++++++++++++++++
python/samba/tests/domain_backup.py | 141 +++++++++++++++++++++++++++++++
selftest/knownfail | 1 +
source4/scripting/bin/samba_backup | 97 ----------------------
source4/selftest/tests.py | 3 +
6 files changed, 304 insertions(+), 97 deletions(-)
create mode 100644 python/samba/netcmd/domain_backup.py
create mode 100644 python/samba/tests/domain_backup.py
delete mode 100755 source4/scripting/bin/samba_backup
diff --git a/python/samba/netcmd/domain.py b/python/samba/netcmd/domain.py
index d3b9b0c..3c90acb 100644
--- a/python/samba/netcmd/domain.py
+++ b/python/samba/netcmd/domain.py
@@ -4325,3 +4325,5 @@ class cmd_domain(SuperCommand):
subcommands["tombstones"] = cmd_domain_tombstones()
subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
subcommands["functionalprep"] = cmd_domain_functional_prep()
+ from domain_backup import cmd_domain_backup
+ subcommands["backup"] = cmd_domain_backup()
diff --git a/python/samba/netcmd/domain_backup.py b/python/samba/netcmd/domain_backup.py
new file mode 100644
index 0000000..120be20
--- /dev/null
+++ b/python/samba/netcmd/domain_backup.py
@@ -0,0 +1,157 @@
+# domain_backup
+#
+# Copyright Andrew Bartlett <abartlet at 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 datetime, os, sys, tarfile, subprocess, logging, shutil, errno
+import samba
+from samba import Ldb
+import tdb
+
+class cmd_domain_backup(samba.netcmd.Command):
+ '''Backup domain directories into a tar with proper locking.'''
+ synopsis = "%prog [options]"
+ takes_optiongroups = {'sambaopts': samba.getopt.SambaOptions}
+
+ takes_options = [
+ samba.netcmd.Option("--skip-encrypted-secrets",
+ help="Don't backup encrypted_secrets.key",
+ action="store_true"),
+ samba.netcmd.Option("--output-dir", help="Output directory", type=str),
+ ]
+
+ def run(self, sambaopts=None, skip_encrypted_secrets=False,
+ output_dir=None):
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+ logger.addHandler(logging.StreamHandler(sys.stdout))
+
+ # Get the absolute paths of all the directories we're going to backup
+ lp = sambaopts.get_loadparm()
+ p = samba.provision.provision_paths_from_lp(lp, lp.get('realm'))
+ assert p.samdb and os.path.exists(p.samdb), 'No sam.db found. This'+\
+ ' backup tool is only for AD DCs'
+
+ dbs_with_children = [p.samdb, p.secrets]
+ up_from_private = os.path.abspath(os.path.join(p.private_dir, '..'))
+ backup_dirs = [p.private_dir, p.state_dir, p.sysvol,
+ os.path.join(up_from_private, 'etc')]
+ logger.info('running backup on dirs: {}'.format(backup_dirs))
+
+ # Recursively get all file paths in the backup directories
+ all_files = [os.path.join(wd,fn) for bd in backup_dirs
+ for (wd,_,fns) in os.walk(bd) for fn in fns]
+ if skip_encrypted_secrets:
+ es_fn = os.path.join(p.private_dir, 'encrypted_secrets.key')
+ logger.info('skipping ' + es_fn)
+ all_files.remove(es_fn)
+
+ # Remove any backup files
+ backup_ext = '.bak'
+ bak_files = [f for f in all_files if f.endswith(backup_ext)]
+ for f in bak_files:
+ all_files.remove(f)
+ os.remove(f)
+
+ # Construct our routine for running the tdbbackup tool.
+ # Find the binary first:
+ tdbbackup_paths = [d for d in [samba.param.bin_dir()] +\
+ os.getenv('PATH').split(os.pathsep)
+ if os.path.exists(os.path.join(d,'tdbbackup'))]
+ assert tdbbackup_paths, 'tdbbackup tool not found'
+ tdbbackup_cmd = [os.path.join(tdbbackup_paths[0], 'tdbbackup'),
+ '-r', '-s', backup_ext]#-r means read locks.
+ def run_tdbbackup(path):
+ status = subprocess.call(tdbbackup_cmd + [path],
+ close_fds=True, shell=False)
+ # If the call failed, bail out unless the errno was EINVAL
+ if status != 0:
+ try:
+ tdb.open(path)
+ except Exception as e:
+ if e.errno != errno.EINVAL:
+ raise e
+ return
+ assert os.path.exists(path + backup_ext), 'tdbbackup said it'+\
+ ' succeeded but it didn\'t '+\
+ 'make a {} file for {}'.format(backup_ext, path)
+
+
+ # Start the backup. First, handle ldb files that have other ldb/tdb
+ # files linked to them.Start a transaction on the ldb file, then
+ # use tdbbackup to back up the DB itself and all linked DBs. If there
+ # are related files that are not DBs, just copy them.
+ private_files = [f for f in all_files if f.startswith(p.private_dir)]
+ for ldb_path in dbs_with_children:
+ ldb_obj = Ldb(ldb_path, lp=lp)
+ logger.info('Starting transaction on ' + ldb_path)
+ ldb_obj.transaction_start()
+
+ db_name = os.path.splitext(os.path.basename(ldb_path))[0]
+ for path in private_files:
+ # All files that start with the same name as a specific LDB
+ # are downstream of it, by convention.
+ if path[len(p.private_dir)+1:].startswith(db_name):
+ if path.endswith('tdb') or path.endswith('ldb'):
+ logger.info(' tdbbackup: locked/related file ' + path)
+ run_tdbbackup(path)
+ else:
+ logger.info(' copy: locked/related file ' + path)
+ shutil.copyfile(path, path + backup_ext)
+ ldb_obj.transaction_cancel()
+
+ # Now handle all the LDB and TDB files that are not linked to
+ # anything else. Use transactions for LDBs.
+ for path in all_files:
+ if not os.path.exists(path + backup_ext):
+ if path.endswith('.ldb'):
+ logger.info('Starting transaction on solo db: ' + path)
+ ldb_obj = Ldb(path, lp=lp)
+ ldb_obj.transaction_start()
+ logger.info(' running tdbbackup on the same file')
+ run_tdbbackup(path)
+ ldb_obj.transaction_cancel()
+ elif path.endswith('.tdb'):
+ logger.info('running tdbbackup on lone tdb file ' + path)
+ run_tdbbackup(path)
+
+ # Now make the backup tar file and add all
+ # backed up files and any other files to it.
+ output_dir = output_dir or os.environ['SELFTEST_PREFIX']
+ temp_tar_name = os.path.join(output_dir, 'INCOMPLETEsambabackupfile')
+ tar = tarfile.open(temp_tar_name, 'w:bz2')
+
+ backup_root = os.path.commonprefix(backup_dirs)
+ logger.info('building backup tar with root ' + backup_root)
+ for path in all_files:
+ arc_path = os.path.relpath(path, backup_root)
+ if os.path.exists(path + backup_ext):
+ logger.info(' adding backup ' + arc_path + backup_ext +\
+ ' to tar and deleting file')
+ tar.add(path + backup_ext, arcname=arc_path)
+ os.remove(path + backup_ext)
+ elif path.endswith('.ldb') or path.endswith('.tdb'):
+ logger.info(' skipping ' + arc_path)
+ else:
+ logger.info(' adding misc file ' + arc_path)
+ tar.add(path, arcname=arc_path)
+
+ tar.close()
+ time_str = datetime.datetime.now().isoformat().replace(':','-')
+ os.rename(temp_tar_name, os.path.join(output_dir,
+ 'samba-backup-{}.tar.bz2'.format(time_str)))
+
+ logger.info('Backup succeeded... Probably.')
diff --git a/python/samba/tests/domain_backup.py b/python/samba/tests/domain_backup.py
new file mode 100644
index 0000000..7f793ee
--- /dev/null
+++ b/python/samba/tests/domain_backup.py
@@ -0,0 +1,141 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett <abartlet at 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/>.
+#
+
+from samba.credentials import Credentials
+from samba import gensec, auth, tests, provision, param
+import tarfile, os
+from samba.tests.samba_tool.base import SambaToolCmdTest
+
+# Test the domain backup tool by setting up a client with the testenv config,
+# then setting up a server on a restored backup. The test passes or fails
+# depending on whether the session keys are the same on the client and server.
+class DomainBackup(SambaToolCmdTest):
+
+ def test_domain_backup_success(self):
+ self.nottest_domain_backup(kill_password=False)
+
+ def test_domain_backup_fail(self):
+ self.nottest_domain_backup(kill_password=True)
+
+ def nottest_domain_backup(self, kill_password=False):
+ client_finished = False
+ server_finished = False
+ server_to_client = b""
+ client_to_server = b""
+
+ # Make a clean temp directory for the backup
+ tmp_backup_dir = os.path.join(os.environ["SELFTEST_PREFIX"],
+ ".backup-test-tmp")
+ if os.path.exists(tmp_backup_dir):
+ import shutil
+ shutil.rmtree(tmp_backup_dir)
+ os.mkdir(tmp_backup_dir)
+
+ # Initialise client and run an update
+ client_lp = tests.env_loadparm()
+ client_settings = {"lp_ctx": client_lp,
+ "target_hostname": client_lp.get("netbios name")}
+ client_lp.set("spnego:simulate_w2k", "no")
+ gensec_client = gensec.Security.start_client(client_settings)
+ gensec_client.set_credentials(self.get_credentials())
+ gensec_client.want_feature(gensec.FEATURE_SEAL)
+ gensec_client.start_mech_by_sasl_name("GSSAPI")
+ result = gensec_client.update(server_to_client)
+ (client_finished, client_to_server) = result
+
+ # Get the root of the backup folders from loadparm, we'll use this later
+ p = provision.provision_paths_from_lp(client_lp, client_lp.get("realm"))
+ backup_dirs = [p.private_dir, p.state_dir, p.sysvol]
+ backup_root = os.path.commonprefix(backup_dirs)
+
+ # Run the backup and check we got one backup tar file
+ (result, out, err) = self.runsubcmd("domain", "backup",
+ "--output-dir", tmp_backup_dir)
+ self.assertCmdSuccess(result, out, err,
+ "Ensuring domain backup command ran successfully")
+ tar_files = [fn for fn in os.listdir(tmp_backup_dir)
+ if fn.startswith("samba-backup-") and
+ fn.endswith(".tar.bz2")]
+ assert len(tar_files) == 1, "expected domain backup to create one" +\
+ "tar file but got {}".format(len(tar_files))
+
+ # Fill the temp backup dir with the empty folder that are needed,
+ # then extract the backup
+ for tmp_subdir_to_make in ["lockdir", "cachedir", "pid"]:
+ os.mkdir(os.path.join(tmp_backup_dir, tmp_subdir_to_make))
+ tf = tarfile.open(os.path.join(tmp_backup_dir, tar_files[0]))
+ tf.extractall(tmp_backup_dir)
+
+ # Make paths in the restored config file point the new temp directory
+ cfg_fn = os.path.join(os.getcwd(), tmp_backup_dir, "etc", "smb.conf")
+ sed_lines = ""
+ with open(cfg_fn, "r") as cfg_file:
+ sed_lines = cfg_file.read().replace(backup_root,
+ os.path.realpath(tmp_backup_dir) + "/")
+ with open(cfg_fn, "w") as cfg_file:
+ cfg_file.write(sed_lines)
+
+ # If this is a test that should fail, we"ll kill the password
+ if kill_password:
+ chgtdcpass_cmd = "./source4/scripting/devel/chgtdcpass -s " + cfg_fn
+ self.check_output(chgtdcpass_cmd)
+ self.check_output(chgtdcpass_cmd)
+
+ # Initialise server to the restored domain
+ server_lp = param.LoadParm()
+ server_settings = {"target_hostname": server_lp.get("netbios name"),
+ "lp_ctx": server_lp}
+ server_lp.set("spnego:simulate_w2k", "no")
+ gensec_server = gensec.Security.start_server(settings=server_settings,
+ auth_context=auth.AuthContext(lp_ctx=server_lp))
+ creds = Credentials()
+ creds.guess(server_lp)
+ creds.set_machine_account(server_lp)
+ gensec_server.set_credentials(creds)
+ gensec_server.want_feature(gensec.FEATURE_SEAL)
+ gensec_server.start_mech_by_sasl_name("GSSAPI")
+
+ exception = None
+ try:
+ # Run update routines until done
+ while True:
+ if not server_finished:
+ print("running server gensec_update")
+ result = gensec_server.update(client_to_server)
+ (server_finished, server_to_client) = result
+
+ if not client_finished:
+ print("running client gensec_update")
+ result = gensec_client.update(server_to_client)
+ (client_finished, client_to_server) = result
+
+ if client_finished and server_finished:
+ break
+ except Exception as e:
+ exception = e
+
+ if kill_password:
+ # If the test was intended to fail, make sure
+ self.assertIsNotNone(exception)
+ else:
+ # If the test was intended to pass, raise any exceptions or
+ # do a final session key equality check.
+ if exception:
+ raise exception
+ client_session_key = gensec_client.session_key()
+ server_session_key = gensec_server.session_key()
+ self.assertEqual(client_session_key, server_session_key)
diff --git a/selftest/knownfail b/selftest/knownfail
index a2aeed2..54a43b7 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -343,3 +343,4 @@
# Disabling NTLM means you can't use samr to change the password
^samba.tests.ntlmdisabled.python\(ktest\).ntlmdisabled.NtlmDisabledTests.test_samr_change_password\(ktest\)
^samba.tests.ntlmdisabled.python\(ad_dc_no_ntlm\).ntlmdisabled.NtlmDisabledTests.test_ntlm_connection\(ad_dc_no_ntlm\)
+.*samba.tests.domain_backup.DomainBackup.test_domain_backup_fail
diff --git a/source4/scripting/bin/samba_backup b/source4/scripting/bin/samba_backup
deleted file mode 100755
index 3e22abe..0000000
--- a/source4/scripting/bin/samba_backup
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) Matthieu Patou <mat at matws.net> 2010-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
-# 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/>.
-#
-# Revised 2013-09-25, Brian Martin, as follows:
-# - Allow retention period ("DAYS") to be specified as a parameter.
-# - Allow individual positional parameters to be left at the default
-# by specifying "-"
-# - Use IS0 8601 standard dates (yyyy-mm-dd instead of mmddyyyy).
-# - Display tar exit codes when reporting errors.
-# - Don't send error messages to /dev/null, so we know what failed.
-# - Suppress useless tar "socket ignored" message.
-# - Fix retention period bug when deleting old backups ($DAYS variable
-# could be set, but was ignored).
-
-
-
-FROMWHERE=/usr/local/samba
-WHERE=/usr/local/backups
-DAYS=90 # Set default retention period.
-if [ -n "$1" ] && [ "$1" = "-h" -o "$1" = "--usage" ]; then
- echo "samba_backup [provisiondir] [destinationdir] [retpd]"
- echo "Will backup your provision located in provisiondir to archive stored"
- echo "in destinationdir for retpd days. Use - to leave an option unchanged."
- echo "Default provisiondir: $FROMWHERE"
- echo "Default destinationdir: $WHERE"
- echo "Default destinationdir: $DAYS"
- exit 0
-fi
-
-[ -n "$1" -a "$1" != "-" ]&&FROMWHERE=$1 # Use parm or default if "-". Validate later.
-[ -n "$2" -a "$2" != "-" ]&&WHERE=$2 # Use parm or default if "-". Validate later.
-[ -n "$3" -a "$3" -eq "$3" 2> /dev/null ]&&DAYS=$3 # Use parm or default if non-numeric (incl "-").
-
-DIRS="private etc sysvol"
-#Number of days to keep the backup
-WHEN=`date +%Y-%m-%d` # ISO 8601 standard date.
-
-if [ ! -d $WHERE ]; then
- echo "Missing backup directory $WHERE"
- exit 1
-fi
-
-if [ ! -d $FROMWHERE ]; then
- echo "Missing or wrong provision directory $FROMWHERE"
- exit 1
-fi
-
-cd $FROMWHERE
-for d in $DIRS;do
- relativedirname=`find . -type d -name "$d" -prune`
- n=`echo $d | sed 's/\//_/g'`
- if [ "$d" = "private" ]; then
- find $relativedirname -name "*.ldb.bak" -exec rm {} \;
- for ldb in `find $relativedirname -name "*.ldb"`; do
- tdbbackup $ldb
- Status=$? # Preserve $? for message, since [ alters it.
- if [ $Status -ne 0 ]; then
- echo "Error while backing up $ldb - status $Status"
- exit 1
- fi
- done
- # Run the backup.
- # --warning=no-file-ignored set to suppress "socket ignored" messages.
- tar cjf ${WHERE}/samba4_${n}.${WHEN}.tar.bz2 $relativedirname --exclude=\*.ldb --warning=no-file-ignored --transform 's/.ldb.bak$/.ldb/'
- Status=$? # Preserve $? for message, since [ alters it.
- if [ $Status -ne 0 -a $Status -ne 1 ]; then # Ignore 1 - private dir is always changing.
- echo "Error while archiving ${WHERE}/samba4_${n}.${WHEN}.tar.bz2 - status = $Status"
- exit 1
- fi
- find $relativedirname -name "*.ldb.bak" -exec rm {} \;
- else
- # Run the backup.
- # --warning=no-file-ignored set to suppress "socket ignored" messages.
- tar cjf ${WHERE}/${n}.${WHEN}.tar.bz2 $relativedirname --warning=no-file-ignored
- Status=$? # Preserve $? for message, since [ alters it.
- if [ $Status -ne 0 ]; then
- echo "Error while archiving ${WHERE}/${n}.${WHEN}.tar.bz2 - status = $Status"
- exit 1
- fi
- fi
-done
-
-find $WHERE -name "samba4_*bz2" -mtime +$DAYS -exec rm {} \;
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index ef752a5..757ce92 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -683,6 +683,9 @@ planoldpythontestsuite("fl2003dc:local",
planoldpythontestsuite("ad_dc",
"samba.tests.password_hash_ldap",
extra_args=['-U"$USERNAME%$PASSWORD"'])
+planoldpythontestsuite("ad_dc:local",
+ "samba.tests.domain_backup",
+ extra_args=['-U"$USERNAME%$PASSWORD"'])
# Encrypted secrets
# ensure default provision (ad_dc) and join (vampire_dc)
# encrypt secret values on disk.
--
2.7.4
From b3fa317ef0f3c164da3496ead3767a370b841917 Mon Sep 17 00:00:00 2001
From: Aaron <aaronhaslett at catalyst.net.nz>
Date: Fri, 23 Mar 2018 15:18:17 +1300
Subject: [PATCH 2/7] auth: keytab invalidation test
Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
---
selftest/knownfail | 1 +
selftest/tests.py | 2 +
source4/auth/tests/kerberos.c | 104 ++++++++++++++++++++++++++++++++++++++++++
source4/auth/wscript_build | 6 +++
4 files changed, 113 insertions(+)
create mode 100644 source4/auth/tests/kerberos.c
diff --git a/selftest/knownfail b/selftest/knownfail
index 54a43b7..c9266ab 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -343,4 +343,5 @@
# Disabling NTLM means you can't use samr to change the password
^samba.tests.ntlmdisabled.python\(ktest\).ntlmdisabled.NtlmDisabledTests.test_samr_change_password\(ktest\)
^samba.tests.ntlmdisabled.python\(ad_dc_no_ntlm\).ntlmdisabled.NtlmDisabledTests.test_ntlm_connection\(ad_dc_no_ntlm\)
+^samba.unittests.kerberos.test_krb5_remove_obsolete_keytab_entries_many
.*samba.tests.domain_backup.DomainBackup.test_domain_backup_fail
diff --git a/selftest/tests.py b/selftest/tests.py
index e69bc31..c209033 100644
--- a/selftest/tests.py
+++ b/selftest/tests.py
@@ -184,5 +184,7 @@ plantestsuite("samba.unittests.tldap", "none",
[os.path.join(bindir(), "default/source3/test_tldap")])
plantestsuite("samba.unittests.rfc1738", "none",
[os.path.join(bindir(), "default/lib/util/test_rfc1738")])
+plantestsuite("samba.unittests.kerberos", "none",
+ [os.path.join(bindir(), "test_kerberos")])
plantestsuite("samba.unittests.ms_fnmatch", "none",
[os.path.join(bindir(), "default/lib/util/test_ms_fnmatch")])
diff --git a/source4/auth/tests/kerberos.c b/source4/auth/tests/kerberos.c
new file mode 100644
index 0000000..7a4441f
--- /dev/null
+++ b/source4/auth/tests/kerberos.c
@@ -0,0 +1,104 @@
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <cmocka.h>
+
+#include "includes.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_proto.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "auth/kerberos/kerberos_credentials.h"
+#include "auth/kerberos/kerberos_util.h"
+
+static void internal_obsolete_keytab_test(int num_principals, int num_kvnos,
+ krb5_kvno kvno, const char *kt_name)
+{
+ krb5_context krb5_ctx;
+ krb5_keytab keytab;
+ krb5_keytab_entry kt_entry;
+ krb5_kt_cursor cursor;
+ krb5_error_code code;
+
+ int i,j;
+ char princ_name[6] = "user0";
+ bool found_previous;
+ const char *error_str;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ krb5_principal *principals = talloc_zero_array(tmp_ctx,
+ krb5_principal,
+ num_principals);
+ krb5_init_context(&krb5_ctx);
+ krb5_kt_resolve(krb5_ctx, kt_name, &keytab);
+ ZERO_STRUCT(kt_entry);
+
+ for(i=0; i<num_principals; i++){
+ princ_name[4] = (char)i+48;
+ krb5_make_principal(krb5_ctx, &(principals[i]),
+ "samba.example.com", princ_name, NULL);
+ kt_entry.principal = principals[i];
+ for(j=0; j<num_kvnos; j++){
+ kt_entry.vno = j+1;
+ krb5_kt_add_entry(krb5_ctx, keytab, &kt_entry);
+ }
+ }
+
+ code = krb5_kt_start_seq_get(krb5_ctx, keytab, &cursor);
+ assert_int_equal(code, 0);
+ for(i=0; i<num_principals; i++){
+ princ_name[4] = (char)i+48;
+ for(j=0; j<num_kvnos; j++){
+ code = krb5_kt_next_entry(krb5_ctx, keytab,
+ &kt_entry, &cursor);
+ assert_int_equal(code, 0);
+ assert_string_equal(princ_name,
+ *(kt_entry.principal->name.name_string.val));
+ assert_int_equal(kt_entry.vno, j+1);
+ }
+ }
+
+ smb_krb5_remove_obsolete_keytab_entries(tmp_ctx, krb5_ctx, keytab,
+ num_principals, principals,
+ kvno, &found_previous,
+ &error_str);
+
+ code = krb5_kt_start_seq_get(krb5_ctx, keytab, &cursor);
+ assert_int_equal(code, 0);
+ for(i=0; i<num_principals; i++){
+ princ_name[4] = (char)i+48;
+ code = krb5_kt_next_entry(krb5_ctx, keytab, &kt_entry, &cursor);
+ assert_int_equal(code, 0);
+ assert_string_equal(princ_name,
+ *(kt_entry.principal->name.name_string.val));
+ assert_int_equal(kt_entry.vno, kvno-1);
+ }
+ code = krb5_kt_next_entry(krb5_ctx, keytab, &kt_entry, &cursor);
+ assert_int_not_equal(code, 0);
+}
+
+static void test_krb5_remove_obsolete_keytab_entries_many(void **state)
+{
+ internal_obsolete_keytab_test(5, 4, (krb5_kvno)5, "MEMORY:LOL2");
+}
+
+static void test_krb5_remove_obsolete_keytab_entries_one(void **state)
+{
+ internal_obsolete_keytab_test(1, 2, (krb5_kvno)3, "MEMORY:LOL");
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_krb5_remove_obsolete_keytab_entries_one),
+ cmocka_unit_test(test_krb5_remove_obsolete_keytab_entries_many),
+ };
+
+ samba_start_debugger();
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/auth/wscript_build b/source4/auth/wscript_build
index 5786fa5..a982476 100644
--- a/source4/auth/wscript_build
+++ b/source4/auth/wscript_build
@@ -42,6 +42,12 @@ bld.SAMBA_SUBSYSTEM('auth4_sam',
deps=''
)
+bld.SAMBA_BINARY('test_kerberos',
+ source='tests/kerberos.c',
+ deps='cmocka authkrb5 krb5samba com_err CREDENTIALS_KRB5',
+ local_include=False,
+ install=False
+ )
for env in bld.gen_python_environments():
pytalloc_util = bld.pyembed_libname('pytalloc-util')
--
2.7.4
From 43ae94946a794597c8ea46bb2cc10525b4b6b33b Mon Sep 17 00:00:00 2001
From: Aaron <aaronhaslett at catalyst.net.nz>
Date: Fri, 23 Mar 2018 15:19:20 +1300
Subject: [PATCH 3/7] netcmd: domain backup offline test with ldapcmp
Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
---
python/samba/tests/domain_backup_offline.py | 71 +++++++++++++++++++++++++++++
source4/selftest/tests.py | 2 +
2 files changed, 73 insertions(+)
create mode 100644 python/samba/tests/domain_backup_offline.py
diff --git a/python/samba/tests/domain_backup_offline.py b/python/samba/tests/domain_backup_offline.py
new file mode 100644
index 0000000..0eb3ba3
--- /dev/null
+++ b/python/samba/tests/domain_backup_offline.py
@@ -0,0 +1,71 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Aaron Haslett<aaronhaslett at catalyst.net.nz> 2018
+#
+# 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 tarfile, os
+from samba.tests.samba_tool.base import SambaToolCmdTest
+
+class DomainBackupOffline(SambaToolCmdTest):
+
+ # Test the "samba-tool domain backup" command with ldapcmp
+ def test_domain_backup_offline(self):
+ # First, make sure we have a clean ./st/.backup-test-tmp directory
+ tmp_backup_dir = os.path.join("st", ".backup-test-tmp")
+ if os.path.exists(tmp_backup_dir):
+ import shutil
+ shutil.rmtree(tmp_backup_dir)
+ if not os.path.exists("st"):
+ os.mkdir("st")
+ os.mkdir(tmp_backup_dir)
+ prov_dir = os.path.join(tmp_backup_dir, "offlinebackuptest")
+ os.mkdir(prov_dir)
+
+ # Provision domain
+ (result, out, err) = self.runsubcmd("domain", "provision",
+ "--domain", "FOO",
+ "--realm", "foo.example.com",
+ "--targetdir", prov_dir,
+ "--use-ntvfs")
+ self.assertCmdSuccess(result, out, err,
+ "Ensuring domain provision command ran successfully")
+
+ # Run the backup and check we got one backup tar file
+ (result, out, err) = self.runsubcmd("domain", "backup",
+ "--output-dir", tmp_backup_dir,
+ "-s", os.path.join(prov_dir, 'etc',
+ 'smb.conf'))
+ self.assertCmdSuccess(result, out, err,
+ "Ensuring domain backup command ran successfully")
+ tar_files = [fn for fn in os.listdir(tmp_backup_dir)
+ if fn.startswith("samba-backup-")
+ and fn.endswith(".tar.bz2")]
+ assert len(tar_files) == 1, "expected domain backup to create one tar"+\
+ " file but got {}".format(len(tar_files))
+
+ # Open the backup and restore it to a new directory
+ tf = tarfile.open(os.path.join(tmp_backup_dir, tar_files[0]))
+ bak_dir = os.path.join(tmp_backup_dir, "extracted_backup")
+ tf.extractall(bak_dir)
+ sam_fn = os.path.join("private", "sam.ldb")
+
+ # Compare the restored sam.ldb with the old one
+ for partition in ["domain", "configuration", "schema",
+ "dnsdomain", "dnsforest"]:
+ url1 = "tdb://" + os.path.join(os.path.realpath(prov_dir), sam_fn)
+ url2 = "tdb://" + os.path.join(os.path.realpath(bak_dir), sam_fn)
+ (result, out, err) = self.runcmd("ldapcmp", url1, url2, partition)
+ self.assertCmdSuccess(result, out, err,
+ "Ensuring ldapcmp command ran successfully")
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 757ce92..134c84c 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -686,6 +686,8 @@ planoldpythontestsuite("ad_dc",
planoldpythontestsuite("ad_dc:local",
"samba.tests.domain_backup",
extra_args=['-U"$USERNAME%$PASSWORD"'])
+planoldpythontestsuite("none",
+ "samba.tests.domain_backup_offline")
# Encrypted secrets
# ensure default provision (ad_dc) and join (vampire_dc)
# encrypt secret values on disk.
--
2.7.4
From f3646dad285e559c70d940517c5939439dae813b Mon Sep 17 00:00:00 2001
From: Aaron <aaronhaslett at catalyst.net.nz>
Date: Fri, 23 Mar 2018 15:20:29 +1300
Subject: [PATCH 4/7] param: adding non-global smb.cfg option
Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
---
lib/param/loadparm.c | 15 +++++++++++++--
python/samba/tests/domain_backup.py | 2 +-
source4/param/pyparam.c | 19 ++++++++++++++++++-
3 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c
index b46700d..c771015 100644
--- a/lib/param/loadparm.c
+++ b/lib/param/loadparm.c
@@ -3145,7 +3145,8 @@ bool lpcfg_load_default(struct loadparm_context *lp_ctx)
*
* Return True on success, False on failure.
*/
-bool lpcfg_load(struct loadparm_context *lp_ctx, const char *filename)
+static bool lpcfg_load_internal(struct loadparm_context *lp_ctx,
+ const char *filename, bool set_global)
{
char *n2;
bool bRetval;
@@ -3180,7 +3181,7 @@ bool lpcfg_load(struct loadparm_context *lp_ctx, const char *filename)
for a missing smb.conf */
reload_charcnv(lp_ctx);
- if (bRetval == true) {
+ if (bRetval == true && set_global) {
/* set this up so that any child python tasks will
find the right smb.conf */
setenv("SMB_CONF_PATH", filename, 1);
@@ -3194,6 +3195,16 @@ bool lpcfg_load(struct loadparm_context *lp_ctx, const char *filename)
return bRetval;
}
+bool lpcfg_load_no_global(struct loadparm_context *lp_ctx, const char *filename)
+{
+ return lpcfg_load_internal(lp_ctx, filename, false);
+}
+
+bool lpcfg_load(struct loadparm_context *lp_ctx, const char *filename)
+{
+ return lpcfg_load_internal(lp_ctx, filename, true);
+}
+
/**
* Return the max number of services.
*/
diff --git a/python/samba/tests/domain_backup.py b/python/samba/tests/domain_backup.py
index 7f793ee..e92b470 100644
--- a/python/samba/tests/domain_backup.py
+++ b/python/samba/tests/domain_backup.py
@@ -96,7 +96,7 @@ class DomainBackup(SambaToolCmdTest):
self.check_output(chgtdcpass_cmd)
# Initialise server to the restored domain
- server_lp = param.LoadParm()
+ server_lp = param.LoadParm(_filename_for_non_global_lp = cfg_fn)
server_settings = {"target_hostname": server_lp.get("netbios name"),
"lp_ctx": server_lp}
server_lp.set("spnego:simulate_w2k", "no")
diff --git a/source4/param/pyparam.c b/source4/param/pyparam.c
index f16c2c0..5807bc7 100644
--- a/source4/param/pyparam.c
+++ b/source4/param/pyparam.c
@@ -420,7 +420,24 @@ static PyGetSetDef py_lp_ctx_getset[] = {
static PyObject *py_lp_ctx_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
- return pytalloc_reference(type, loadparm_init_global(false));
+ const char *kwnames[] = {"_filename_for_non_global_lp", NULL};
+ PyObject *lp_ctx;
+ const char *conf_filename = NULL;
+ struct loadparm_context *ctx;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s",
+ discard_const_p(char *, kwnames), &conf_filename)){
+ return NULL;
+ }
+ if (conf_filename != NULL){
+ ctx = loadparm_init(NULL);
+ lp_ctx = pytalloc_reference(type, ctx);
+ lpcfg_load_no_global(PyLoadparmContext_AsLoadparmContext(lp_ctx),
+ conf_filename);
+ return lp_ctx;
+ } else{
+ return pytalloc_reference(type, loadparm_init_global(false));
+ }
}
static Py_ssize_t py_lp_ctx_len(PyObject *self)
--
2.7.4
From 395391588e8ae81673ba643d99faeae2eb22e7e1 Mon Sep 17 00:00:00 2001
From: Aaron <aaronhaslett at catalyst.net.nz>
Date: Fri, 23 Mar 2018 15:21:02 +1300
Subject: [PATCH 5/7] ldb: removing prior secret from logs
Bug: https://bugzilla.samba.org/show_bug.cgi?id=13353
Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
---
lib/ldb-samba/ldif_handlers.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/ldb-samba/ldif_handlers.c b/lib/ldb-samba/ldif_handlers.c
index 591bd1e..ecc02e5 100644
--- a/lib/ldb-samba/ldif_handlers.c
+++ b/lib/ldb-samba/ldif_handlers.c
@@ -1706,7 +1706,8 @@ const struct ldb_schema_syntax *ldb_samba_syntax_by_lDAPDisplayName(struct ldb_c
return s;
}
-static const char *secret_attributes[] = {DSDB_SECRET_ATTRIBUTES, "secret", NULL};
+static const char *secret_attributes[] = {DSDB_SECRET_ATTRIBUTES, "secret",
+ "priorSecret", NULL};
/*
register the samba ldif handlers
--
2.7.4
From ce48e15c97bb8f29a3796a216281f852158c84be Mon Sep 17 00:00:00 2001
From: Aaron <aaronhaslett at catalyst.net.nz>
Date: Fri, 23 Mar 2018 15:22:05 +1300
Subject: [PATCH 6/7] tdb: adding readonly locks mode to tdbbackup tool
Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
---
lib/tdb/tools/tdbbackup.c | 31 ++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/lib/tdb/tools/tdbbackup.c b/lib/tdb/tools/tdbbackup.c
index eb33e25..2689b6f 100644
--- a/lib/tdb/tools/tdbbackup.c
+++ b/lib/tdb/tools/tdbbackup.c
@@ -105,7 +105,7 @@ static int test_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
this function is also used for restore
*/
static int backup_tdb(const char *old_name, const char *new_name,
- int hash_size, int nolock)
+ int hash_size, int nolock, int readonly)
{
TDB_CONTEXT *tdb;
TDB_CONTEXT *tdb_new;
@@ -145,8 +145,17 @@ static int backup_tdb(const char *old_name, const char *new_name,
return 1;
}
- if (tdb_transaction_start(tdb) != 0) {
- printf("Failed to start transaction on old tdb\n");
+ if (readonly){
+ if(tdb_lockall_read(tdb) != 0){
+ printf("Failed to readonly lock on old tdb\n");
+ tdb_close(tdb);
+ tdb_close(tdb_new);
+ unlink(tmp_name);
+ free(tmp_name);
+ return 1;
+ }
+ }else if(tdb_transaction_start(tdb) != 0){
+ printf("Failed to start transaction on db\n");
tdb_close(tdb);
tdb_close(tdb_new);
unlink(tmp_name);
@@ -167,7 +176,11 @@ static int backup_tdb(const char *old_name, const char *new_name,
failed = 0;
/* traverse and copy */
- count1 = tdb_traverse(tdb, copy_fn, (void *)tdb_new);
+ if(readonly){
+ count1 = tdb_traverse_read(tdb, copy_fn, (void *)tdb_new);
+ }else{
+ count1 = tdb_traverse(tdb, copy_fn, (void *)tdb_new);
+ }
if (count1 < 0 || failed) {
fprintf(stderr,"failed to copy %s\n", old_name);
tdb_close(tdb);
@@ -251,7 +264,7 @@ static int verify_tdb(const char *fname, const char *bak_name)
/* count is < 0 means an error */
if (count < 0) {
printf("restoring %s\n", fname);
- return backup_tdb(bak_name, fname, 0, 0);
+ return backup_tdb(bak_name, fname, 0, 0, 0);
}
printf("%s : %d records\n", fname, count);
@@ -282,6 +295,7 @@ static void usage(void)
printf(" -v verify mode (restore if corrupt)\n");
printf(" -n hashsize set the new hash size for the backup\n");
printf(" -l open without locking to back up mutex dbs\n");
+ printf(" -r open with read only locking\n");
}
int main(int argc, char *argv[])
@@ -292,11 +306,12 @@ static void usage(void)
int verify = 0;
int hashsize = 0;
int nolock = 0;
+ int readonly = 0;
const char *suffix = ".bak";
log_ctx.log_fn = tdb_log;
- while ((c = getopt(argc, argv, "vhs:n:l")) != -1) {
+ while ((c = getopt(argc, argv, "vhs:n:lr")) != -1) {
switch (c) {
case 'h':
usage();
@@ -313,6 +328,8 @@ static void usage(void)
case 'l':
nolock = 1;
break;
+ case 'r':
+ readonly = 1;
}
}
@@ -337,7 +354,7 @@ static void usage(void)
} else {
if (file_newer(fname, bak_name) &&
backup_tdb(fname, bak_name, hashsize,
- nolock) != 0) {
+ nolock, readonly) != 0) {
ret = 1;
}
}
--
2.7.4
From dd46e05e9bb4fbd600a2dd2b06ffc5b397665808 Mon Sep 17 00:00:00 2001
From: Aaron <aaronhaslett at catalyst.net.nz>
Date: Fri, 23 Mar 2018 15:23:11 +1300
Subject: [PATCH 7/7] auth: keytab invalidation fix
Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
---
selftest/knownfail | 2 --
source4/auth/kerberos/kerberos_util.c | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/selftest/knownfail b/selftest/knownfail
index c9266ab..a2aeed2 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -343,5 +343,3 @@
# Disabling NTLM means you can't use samr to change the password
^samba.tests.ntlmdisabled.python\(ktest\).ntlmdisabled.NtlmDisabledTests.test_samr_change_password\(ktest\)
^samba.tests.ntlmdisabled.python\(ad_dc_no_ntlm\).ntlmdisabled.NtlmDisabledTests.test_ntlm_connection\(ad_dc_no_ntlm\)
-^samba.unittests.kerberos.test_krb5_remove_obsolete_keytab_entries_many
-.*samba.tests.domain_backup.DomainBackup.test_domain_backup_fail
diff --git a/source4/auth/kerberos/kerberos_util.c b/source4/auth/kerberos/kerberos_util.c
index 618da62..50bf8fe 100644
--- a/source4/auth/kerberos/kerberos_util.c
+++ b/source4/auth/kerberos/kerberos_util.c
@@ -633,7 +633,7 @@ krb5_error_code smb_krb5_remove_obsolete_keytab_entries(TALLOC_CTX *mem_ctx,
krb5_kt_free_entry(context, &entry);
/* Make sure we do not double free */
ZERO_STRUCT(entry);
- } while (code != 0);
+ } while (code == 0);
krb5_kt_end_seq_get(context, keytab, &cursor);
--
2.7.4
More information about the samba-technical
mailing list