From cf1f86394f40cad9951b346cc55bb62eaec32767 Mon Sep 17 00:00:00 2001 From: Uri Simchoni Date: Sun, 1 Nov 2015 22:28:46 +0200 Subject: [PATCH 1/3] vfs_shadow_copy2: add shadow_copy2_do_convert() Add a new routine shadow_copy2_do_convert() which is like shadow_copy2_convert(), but beside calculating the path of the snapshot file, it also returns the minimum length of the subpath which is both inside the share and inside the same snapshot as the file in question, i.e. (at least in the classical case) the subdirectory of the the snapshot file's snapshot directory that corresponds to the file's share root. BUG: https://bugzilla.samba.org/show_bug.cgi?id=11580 Signed-off-by: Uri Simchoni Reviewed-by: Michael Adam (cherry picked from commit 3703bca4d9e494aec0b40243add3e076cf353601) --- source3/modules/vfs_shadow_copy2.c | 39 ++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/source3/modules/vfs_shadow_copy2.c b/source3/modules/vfs_shadow_copy2.c index d1673a4..99eb2c4 100644 --- a/source3/modules/vfs_shadow_copy2.c +++ b/source3/modules/vfs_shadow_copy2.c @@ -434,10 +434,13 @@ static char *shadow_copy2_find_mount_point(TALLOC_CTX *mem_ctx, * Convert from a name as handed in via the SMB layer * and a timestamp into the local path of the snapshot * of the provided file at the provided time. + * Also return the path in the snapshot corresponding + * to the file's share root. */ -static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, - struct vfs_handle_struct *handle, - const char *name, time_t timestamp) +static char *shadow_copy2_do_convert(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const char *name, time_t timestamp, + size_t *snaproot_len) { struct smb_filename converted_fname; char *result = NULL; @@ -447,10 +450,11 @@ static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, size_t pathlen; char *insert = NULL; char *converted = NULL; - size_t insertlen; + size_t insertlen, connectlen = 0; int i, saved_errno; size_t min_offset; struct shadow_copy2_config *config; + size_t in_share_offset = 0; SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config, return NULL); @@ -492,6 +496,13 @@ static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, DEBUG(10, ("Found %s\n", converted)); result = converted; converted = NULL; + if (snaproot_len != NULL) { + *snaproot_len = strlen(snapshot_path); + if (config->rel_connectpath != NULL) { + *snaproot_len += + strlen(config->rel_connectpath) + 1; + } + } goto fail; } else { errno = ENOENT; @@ -500,6 +511,7 @@ static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, /* never reached ... */ } + connectlen = strlen(handle->conn->connectpath); if (name[0] == 0) { path = talloc_strdup(mem_ctx, handle->conn->connectpath); } else { @@ -575,6 +587,10 @@ static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, goto fail; } + if (offset >= connectlen) { + in_share_offset = offset; + } + memcpy(converted+offset, insert, insertlen); offset += insertlen; @@ -588,6 +604,9 @@ static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, ret, ret == 0 ? "ok" : strerror(errno))); if (ret == 0) { /* success */ + if (snaproot_len != NULL) { + *snaproot_len = in_share_offset + insertlen; + } break; } if (errno == ENOTDIR) { @@ -624,6 +643,18 @@ fail: return result; } +/** + * Convert from a name as handed in via the SMB layer + * and a timestamp into the local path of the snapshot + * of the provided file at the provided time. + */ +static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const char *name, time_t timestamp) +{ + return shadow_copy2_do_convert(mem_ctx, handle, name, timestamp, NULL); +} + /* modify a sbuf return to ensure that inodes in the shadow directory are different from those in the main directory -- 2.4.3 From 08904284536ca56c904ac71746146e06cc98ec7e Mon Sep 17 00:00:00 2001 From: Uri Simchoni Date: Mon, 2 Nov 2015 09:08:53 +0200 Subject: [PATCH 2/3] vfs_shadow_copy2: fix case where snapshots are outside the share Adjust the connect path to the root of the share in the snapshot, or to the root of the snapshot if the snapshot is "inside" the share. This way snapshot symlink regarded as "wide links" if and only if they point outside the snapshot or they were wide links when the snapshot was taken. This allows mounting the snapshots outside the share's root. BUG: https://bugzilla.samba.org/show_bug.cgi?id=11580 Signed-off-by: Uri Simchoni Reviewed-by: Michael Adam (cherry picked from commit 8a49a63a5a5d8014d32179df1789186223443b35) --- source3/modules/vfs_shadow_copy2.c | 68 ++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/source3/modules/vfs_shadow_copy2.c b/source3/modules/vfs_shadow_copy2.c index 99eb2c4..7ecdda5 100644 --- a/source3/modules/vfs_shadow_copy2.c +++ b/source3/modules/vfs_shadow_copy2.c @@ -1134,8 +1134,6 @@ static char *shadow_copy2_realpath(vfs_handle_struct *handle, char *stripped = NULL; char *tmp = NULL; char *result = NULL; - char *inserted = NULL; - char *inserted_to, *inserted_end; int saved_errno; if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, @@ -1152,29 +1150,9 @@ static char *shadow_copy2_realpath(vfs_handle_struct *handle, } result = SMB_VFS_NEXT_REALPATH(handle, tmp); - if (result == NULL) { - goto done; - } - - /* - * Take away what we've inserted. This removes the @GMT-thingy - * completely, but will give a path under the share root. - */ - inserted = shadow_copy2_insert_string(talloc_tos(), handle, timestamp); - if (inserted == NULL) { - goto done; - } - inserted_to = strstr_m(result, inserted); - if (inserted_to == NULL) { - DEBUG(2, ("SMB_VFS_NEXT_REALPATH removed %s\n", inserted)); - goto done; - } - inserted_end = inserted_to + talloc_get_size(inserted) - 1; - memmove(inserted_to, inserted_end, strlen(inserted_end)+1); done: saved_errno = errno; - TALLOC_FREE(inserted); TALLOC_FREE(tmp); TALLOC_FREE(stripped); errno = saved_errno; @@ -1808,6 +1786,51 @@ static int shadow_copy2_get_real_filename(struct vfs_handle_struct *handle, return ret; } +static const char *shadow_copy2_connectpath(struct vfs_handle_struct *handle, + const char *fname) +{ + time_t timestamp; + char *stripped = NULL; + char *tmp = NULL; + char *result = NULL; + int saved_errno; + size_t rootpath_len = 0; + + DBG_DEBUG("Calc connect path for [%s]\n", fname); + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + goto done; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CONNECTPATH(handle, fname); + } + + tmp = shadow_copy2_do_convert(talloc_tos(), handle, stripped, timestamp, + &rootpath_len); + if (tmp == NULL) { + goto done; + } + + DBG_DEBUG("converted path is [%s] root path is [%.*s]\n", tmp, + (int)rootpath_len, tmp); + + tmp[rootpath_len] = '\0'; + result = SMB_VFS_NEXT_REALPATH(handle, tmp); + if (result == NULL) { + goto done; + } + + DBG_DEBUG("connect path is [%s]\n", result); + +done: + saved_errno = errno; + TALLOC_FREE(tmp); + TALLOC_FREE(stripped); + errno = saved_errno; + return result; +} + static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle, const char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) @@ -2109,6 +2132,7 @@ static struct vfs_fn_pointers vfs_shadow_copy2_fns = { .chmod_acl_fn = shadow_copy2_chmod_acl, .chflags_fn = shadow_copy2_chflags, .get_real_filename_fn = shadow_copy2_get_real_filename, + .connectpath_fn = shadow_copy2_connectpath, }; NTSTATUS vfs_shadow_copy2_init(void); -- 2.4.3 From 2bc70cebb95f2c29ff12d5373c8a29f1c9c37759 Mon Sep 17 00:00:00 2001 From: Uri Simchoni Date: Thu, 29 Oct 2015 22:24:30 +0200 Subject: [PATCH 3/3] vfs_shadow_copy2: add a blackbox test suite Add a blackbox test suite for vfs_shadow_copy2, testing parameters mountpoint, basedir, snapdir, snapdirseverywhere, and testing correct wide-link processing. BUG: https://bugzilla.samba.org/show_bug.cgi?id=11580 Signed-off-by: Uri Simchoni Reviewed-by: Michael Adam Autobuild-User(master): Michael Adam Autobuild-Date(master): Wed Jan 13 17:11:38 CET 2016 on sn-devel-144 (cherry picked from commit 7362c27a62e3802fc8df975ce50115b683811f4a) --- selftest/target/Samba3.pm | 69 ++++++++ source3/script/tests/test_shadow_copy.sh | 290 +++++++++++++++++++++++++++++++ source3/selftest/tests.py | 1 + 3 files changed, 360 insertions(+) create mode 100755 source3/script/tests/test_shadow_copy.sh diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index 931667e..80b7517 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -1092,6 +1092,15 @@ sub provision($$$$$$$$) my $manglenames_shrdir="$shrdir/manglenames"; push(@dirs,$manglenames_shrdir); + my $shadow_tstdir="$shrdir/shadow"; + push(@dirs,$shadow_tstdir); + my $shadow_mntdir="$shadow_tstdir/mount"; + push(@dirs,$shadow_mntdir); + my $shadow_basedir="$shadow_mntdir/base"; + push(@dirs,$shadow_basedir); + my $shadow_shrdir="$shadow_basedir/share"; + push(@dirs,$shadow_shrdir); + # this gets autocreated by winbindd my $wbsockdir="$prefix_abs/winbindd"; my $wbsockprivdir="$lockdir/winbindd_privileged"; @@ -1336,6 +1345,10 @@ sub provision($$$$$$$$) # fruit:copyfile is a global option fruit:copyfile = yes + #this does not mean that we use non-secure test env, + #it just means we ALLOW one to be configured. + allow insecure wide links = yes + # Begin extra options $extra_options # End extra options @@ -1492,6 +1505,62 @@ sub provision($$$$$$$$) shell_snap:delete command = $fake_snap_pl --delete # a relative path here fails, the snapshot dir is no longer found shadow:snapdir = $shrdir/.snapshots + +[shadow1] + path = $shadow_shrdir + comment = previous versions snapshots under mount point + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_mntdir + +[shadow2] + path = $shadow_shrdir + comment = previous versions snapshots outside mount point + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_mntdir + shadow:snapdir = $shadow_tstdir/.snapshots + +[shadow3] + path = $shadow_shrdir + comment = previous versions with subvolume snapshots, snapshots under base dir + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_mntdir + shadow:basedir = $shadow_basedir + shadow:snapdir = $shadow_basedir/.snapshots + +[shadow4] + path = $shadow_shrdir + comment = previous versions with subvolume snapshots, snapshots outside mount point + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_mntdir + shadow:basedir = $shadow_basedir + shadow:snapdir = $shadow_tstdir/.snapshots + +[shadow5] + path = $shadow_shrdir + comment = previous versions at volume root snapshots under mount point + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_shrdir + +[shadow6] + path = $shadow_shrdir + comment = previous versions at volume root snapshots outside mount point + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_shrdir + shadow:snapdir = $shadow_tstdir/.snapshots + +[shadow7] + path = $shadow_shrdir + comment = previous versions snapshots everywhere + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_mntdir + shadow:snapdirseverywhere = yes + +[shadow_wl] + path = $shadow_shrdir + comment = previous versions with wide links allowed + vfs objects = shadow_copy2 + shadow:mountpoint = $shadow_mntdir + wide links = yes "; close(CONF); diff --git a/source3/script/tests/test_shadow_copy.sh b/source3/script/tests/test_shadow_copy.sh new file mode 100755 index 0000000..eecd5b8 --- /dev/null +++ b/source3/script/tests/test_shadow_copy.sh @@ -0,0 +1,290 @@ +#!/bin/bash +# +# Blackbox test for shadow_copy2 VFS. +# + +if [ $# -lt 7 ]; then +cat </dev/null 2>&1 + build_snapshots $WORKDIR/$where "$prefix" 0 2 + + testit "$msg - regular file" \ + test_count_versions $share foo $ncopies_allowed || \ + failed=`expr $failed + 1` + + testit "$msg - regular file in subdir" \ + test_count_versions $share bar/baz $ncopies_allowed || \ + failed=`expr $failed + 1` + + testit "$msg - local symlink" \ + test_count_versions $share bar/lfoo $ncopies_allowed || \ + failed=`expr $failed + 1` + + testit "$msg - abs symlink outside" \ + test_count_versions $share bar/letcpasswd $ncopies_blocked || \ + failed=`expr $failed + 1` + + testit "$msg - rel symlink outside" \ + test_count_versions $share bar/loutside $ncopies_blocked || \ + failed=`expr $failed + 1` +} + +test_shadow_copy_everywhere() +{ + local share #share to contact + + share=$1 + + #delete snapshots from previous tests + find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1 + build_snapshots "$WORKDIR/mount" "base/share" 0 0 + build_snapshots "$WORKDIR/mount/base" "share" 1 2 + build_snapshots "$WORKDIR/mount/base/share" "" 3 5 + build_snapshots "$WORKDIR/mount/base/share/bar" "" 6 9 1 + + testit "snapshots in each dir - regular file" \ + test_count_versions $share foo 4 || \ + failed=`expr $failed + 1` + + testit "snapshots in each dir - regular file in subdir" \ + test_count_versions $share bar/baz 5 || \ + failed=`expr $failed + 1` + + testit "snapshots in each dir - local symlink (but outside snapshot)" \ + test_count_versions $share bar/lfoo 1 || \ + failed=`expr $failed + 1` + + testit "snapshots in each dir - abs symlink outside" \ + test_count_versions $share bar/letcpasswd 1 || \ + failed=`expr $failed + 1` + + testit "snapshots in each dir - rel symlink outside" \ + test_count_versions $share bar/loutside 1 || \ + failed=`expr $failed + 1` + + #the previous versions of the file bar/lfoo points to are outside its + #snapshot, and are not reachable. However, but previous versions + #taken at different, non-overlapping times higher up the + #hierarchy are still reachable. + testit "fetch a previous version of a regular file" \ + test_fetch_snap_file $share "bar/baz" 6 || \ + failed=`expr $failed + 1` + + testit_expect_failure "fetch a (non-existent) previous version of a symlink" \ + test_fetch_snap_file $share "bar/lfoo" 6 || \ + failed=`expr $failed + 1` + + testit "fetch a previous version of a symlink via browsing (1)" \ + test_fetch_snap_file $share "bar/lfoo" 0 || \ + failed=`expr $failed + 1` + + testit "fetch a previous version of a symlink via browsing (2)" \ + test_fetch_snap_file $share "bar/lfoo" 1 || \ + failed=`expr $failed + 1` + + testit "fetch a previous version of a symlink via browsing (3)" \ + test_fetch_snap_file $share "bar/lfoo" 3 || \ + failed=`expr $failed + 1` + +} + +#build "latest" files +build_files $WORKDIR/mount base/share "latest" + +failed=0 + +# a test with wide links allowed - also to verify that what's later +# being blocked is a result of server security measures and not +# a testing artifact. +test_shadow_copy_fixed shadow_wl mount base/share "shadow copies with wide links allowed" 1 + +# tests for a fixed snapshot location +test_shadow_copy_fixed shadow1 mount base/share "full volume snapshots mounted under volume" +test_shadow_copy_fixed shadow2 . base/share "full volume snapshots mounted outside volume" +test_shadow_copy_fixed shadow3 mount/base share "sub volume snapshots mounted under snapshot point" +test_shadow_copy_fixed shadow4 . share "sub volume snapshots mounted outside" +test_shadow_copy_fixed shadow5 mount/base/share "" "full volume snapshots and share mounted under volume" +test_shadow_copy_fixed shadow6 . "" "full volume snapshots and share mounted outside" + +# tests for snapshot everywhere - one snapshot location +test_shadow_copy_fixed shadow7 mount base/share "'everywhere' full volume snapshots" +test_shadow_copy_fixed shadow7 mount/base share "'everywhere' sub volume snapshots" +test_shadow_copy_fixed shadow7 mount/base/share "" "'everywhere' share snapshots" + +# a test for snapshots everywhere - multiple snapshot locations +test_shadow_copy_everywhere shadow7 + +exit $failed diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py index 5727844..4a26c25 100755 --- a/source3/selftest/tests.py +++ b/source3/selftest/tests.py @@ -177,6 +177,7 @@ for env in ["nt4_dc"]: for env in ["fileserver"]: plantestsuite("samba3.blackbox.preserve_case (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_preserve_case.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3]) plantestsuite("samba3.blackbox.dfree_command (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_dfree_command.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3]) + plantestsuite("samba3.blackbox.shadow_copy2 (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_shadow_copy.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/shadow', smbclient3]) # # tar command tests -- 2.4.3