diff options
Diffstat (limited to 'source4/torture/smb2')
50 files changed, 69901 insertions, 0 deletions
diff --git a/source4/torture/smb2/acls.c b/source4/torture/smb2/acls.c new file mode 100644 index 0000000..5a892d0 --- /dev/null +++ b/source4/torture/smb2/acls.c @@ -0,0 +1,3340 @@ +/* + Unix SMB/CIFS implementation. + + test security descriptor operations for SMB2 + + Copyright (C) Zack Kirsch 2009 + + 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/>. +*/ + +#include "includes.h" +#include "lib/cmdline/cmdline.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb/smbXcli_base.h" +#include "torture/torture.h" +#include "libcli/resolve/resolve.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "lib/param/param.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define BASEDIR "smb2-testsd" + +#define CHECK_ACCESS_IGNORE SEC_STD_SYNCHRONIZE + +#define CHECK_ACCESS_FLAGS(_fh, flags) do { \ + union smb_fileinfo _q; \ + _q.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; \ + _q.access_information.in.file.handle = (_fh); \ + status = smb2_getinfo_file(tree, tctx, &_q); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + /* Handle a Vista bug where SEC_STD_SYNCHRONIZE doesn't come back. */ \ + if ((((flags) & CHECK_ACCESS_IGNORE) == CHECK_ACCESS_IGNORE) && \ + ((_q.access_information.out.access_flags & CHECK_ACCESS_IGNORE) != CHECK_ACCESS_IGNORE)) { \ + torture_comment(tctx, "SKIPPING (Vista bug): (%s) Incorrect access_flags 0x%08x - should be 0x%08x\n", \ + __location__, _q.access_information.out.access_flags, (flags)); \ + } \ + if ((_q.access_information.out.access_flags & ~CHECK_ACCESS_IGNORE) != \ + (((flags) & ~CHECK_ACCESS_IGNORE))) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect access_flags 0x%08x - should be 0x%08x\n", \ + __location__, _q.access_information.out.access_flags, (flags)); \ + ret = false; \ + goto done; \ + } \ +} while (0) + +#define FAIL_UNLESS(__cond) \ + do { \ + if (__cond) {} else { \ + torture_result(tctx, TORTURE_FAIL, "%s) condition violated: %s\n", \ + __location__, #__cond); \ + ret = false; goto done; \ + } \ + } while(0) + +#define CHECK_SECURITY_DESCRIPTOR(_sd1, _sd2) do { \ + if (!security_descriptor_equal(_sd1, _sd2)) { \ + torture_warning(tctx, "security descriptors don't match!\n"); \ + torture_warning(tctx, "got:\n"); \ + NDR_PRINT_DEBUG(security_descriptor, _sd1); \ + torture_warning(tctx, "expected:\n"); \ + NDR_PRINT_DEBUG(security_descriptor, _sd2); \ + torture_result(tctx, TORTURE_FAIL, \ + "%s: security descriptors don't match!\n", \ + __location__); \ + ret = false; \ + } \ +} while (0) + +/* + test the behaviour of the well known SID_CREATOR_OWNER sid, and some generic + mapping bits + Note: This test was copied from raw/acls.c. +*/ +static bool test_creator_sid(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *fname = BASEDIR "\\creator.txt"; + bool ret = true; + struct smb2_handle handle = {{0}}; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig, *sd2; + const char *owner_sid; + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + torture_comment(tctx, "TESTING SID_CREATOR_OWNER\n"); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_STD_READ_CONTROL | SEC_STD_WRITE_DAC | SEC_STD_WRITE_OWNER; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + torture_comment(tctx, "set a sec desc allowing no write by CREATOR_OWNER\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "try open for write\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "try open for read\n"); + io.in.desired_access = SEC_FILE_READ_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "try open for generic write\n"); + io.in.desired_access = SEC_GENERIC_WRITE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "try open for generic read\n"); + io.in.desired_access = SEC_GENERIC_READ; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "set a sec desc allowing no write by owner\n"); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "check that sd has been mapped correctly\n"); + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd); + + torture_comment(tctx, "try open for write\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "try open for read\n"); + io.in.desired_access = SEC_FILE_READ_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_FILE_READ_DATA); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "try open for generic write\n"); + io.in.desired_access = SEC_GENERIC_WRITE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "try open for generic read\n"); + io.in.desired_access = SEC_GENERIC_READ; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_RIGHTS_FILE_READ); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "set a sec desc allowing generic read by owner\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_READ | SEC_STD_ALL, + 0, + NULL); + + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "check that generic read has been mapped correctly\n"); + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + torture_comment(tctx, "try open for write\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "try open for read\n"); + io.in.desired_access = SEC_FILE_READ_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_FILE_READ_DATA); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "try open for generic write\n"); + io.in.desired_access = SEC_GENERIC_WRITE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "try open for generic read\n"); + io.in.desired_access = SEC_GENERIC_READ; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, SEC_RIGHTS_FILE_READ); + smb2_util_close(tree, io.out.file.handle); + + + torture_comment(tctx, "put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + +done: + smb2_util_close(tree, handle); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + + +/* + test the mapping of the SEC_GENERIC_xx bits to SEC_STD_xx and + SEC_FILE_xx bits + Note: This test was copied from raw/acls.c. +*/ +static bool test_generic_bits(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *fname = BASEDIR "\\generic.txt"; + bool ret = true; + struct smb2_handle handle = {{0}}; + int i; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig, *sd2; + const char *owner_sid; + const struct { + uint32_t gen_bits; + uint32_t specific_bits; + } file_mappings[] = { + { 0, 0 }, + { SEC_GENERIC_READ, SEC_RIGHTS_FILE_READ }, + { SEC_GENERIC_WRITE, SEC_RIGHTS_FILE_WRITE }, + { SEC_GENERIC_EXECUTE, SEC_RIGHTS_FILE_EXECUTE }, + { SEC_GENERIC_ALL, SEC_RIGHTS_FILE_ALL }, + { SEC_FILE_READ_DATA, SEC_FILE_READ_DATA }, + { SEC_FILE_READ_ATTRIBUTE, SEC_FILE_READ_ATTRIBUTE } + }; + const struct { + uint32_t gen_bits; + uint32_t specific_bits; + } dir_mappings[] = { + { 0, 0 }, + { SEC_GENERIC_READ, SEC_RIGHTS_DIR_READ }, + { SEC_GENERIC_WRITE, SEC_RIGHTS_DIR_WRITE }, + { SEC_GENERIC_EXECUTE, SEC_RIGHTS_DIR_EXECUTE }, + { SEC_GENERIC_ALL, SEC_RIGHTS_DIR_ALL } + }; + bool has_restore_privilege = false; + bool has_take_ownership_privilege = false; + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + torture_comment(tctx, "TESTING FILE GENERIC BITS\n"); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + +/* + * XXX: The smblsa calls use SMB as their transport - need to get rid of + * dependency. + */ +/* + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_RESTORE)); + has_restore_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + torture_comment(tctx, "SEC_PRIV_RESTORE - %s\n", has_restore_privilege?"Yes":"No"); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_TAKE_OWNERSHIP)); + has_take_ownership_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + torture_comment(tctx, "SEC_PRIV_TAKE_OWNERSHIP - %s\n", has_take_ownership_privilege?"Yes":"No"); +*/ + + for (i=0;i<ARRAY_SIZE(file_mappings);i++) { + uint32_t expected_mask = + SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | + SEC_FILE_READ_ATTRIBUTE | + SEC_STD_DELETE; + uint32_t expected_mask_anon = SEC_FILE_READ_ATTRIBUTE; + + if (has_restore_privilege) { + expected_mask_anon |= SEC_STD_DELETE; + } + + torture_comment(tctx, "Testing generic bits 0x%08x\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].specific_bits, + 0, + NULL); + + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + expected_mask | file_mappings[i].specific_bits); + smb2_util_close(tree, io.out.file.handle); + + if (!has_take_ownership_privilege) { + continue; + } + + torture_comment(tctx, "Testing generic bits 0x%08x (anonymous)\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].specific_bits, + 0, + NULL); + + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + expected_mask_anon | file_mappings[i].specific_bits); + smb2_util_close(tree, io.out.file.handle); + } + + torture_comment(tctx, "put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, handle); + smb2_util_unlink(tree, fname); + + + torture_comment(tctx, "TESTING DIR GENERIC BITS\n"); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + +/* + * XXX: The smblsa calls use SMB as their transport - need to get rid of + * dependency. + */ +/* + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_RESTORE)); + has_restore_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + torture_comment(tctx, "SEC_PRIV_RESTORE - %s\n", has_restore_privilege?"Yes":"No"); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_TAKE_OWNERSHIP)); + has_take_ownership_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + torture_comment(tctx, "SEC_PRIV_TAKE_OWNERSHIP - %s\n", has_take_ownership_privilege?"Yes":"No"); + +*/ + for (i=0;i<ARRAY_SIZE(dir_mappings);i++) { + uint32_t expected_mask = + SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | + SEC_FILE_READ_ATTRIBUTE | + SEC_STD_DELETE; + uint32_t expected_mask_anon = SEC_FILE_READ_ATTRIBUTE; + + if (has_restore_privilege) { + expected_mask_anon |= SEC_STD_DELETE; + } + + torture_comment(tctx, "Testing generic bits 0x%08x\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + dir_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + dir_mappings[i].specific_bits, + 0, + NULL); + + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + expected_mask | dir_mappings[i].specific_bits); + smb2_util_close(tree, io.out.file.handle); + + if (!has_take_ownership_privilege) { + continue; + } + + torture_comment(tctx, "Testing generic bits 0x%08x (anonymous)\n", + file_mappings[i].gen_bits); + sd = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].gen_bits, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + sd2 = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + file_mappings[i].specific_bits, + 0, + NULL); + + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + expected_mask_anon | dir_mappings[i].specific_bits); + smb2_util_close(tree, io.out.file.handle); + } + + torture_comment(tctx, "put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, handle); + smb2_util_unlink(tree, fname); + +done: + smb2_util_close(tree, handle); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + + +/* + see what access bits the owner of a file always gets + Note: This test was copied from raw/acls.c. +*/ +static bool test_owner_bits(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *fname = BASEDIR "\\test_owner_bits.txt"; + bool ret = true; + struct smb2_handle handle = {{0}}; + int i; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig; + const char *owner_sid; + uint32_t expected_bits; + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + torture_comment(tctx, "TESTING FILE OWNER BITS\n"); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + +/* + * XXX: The smblsa calls use SMB as their transport - need to get rid of + * dependency. + */ +/* + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_RESTORE)); + has_restore_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + torture_comment(tctx, "SEC_PRIV_RESTORE - %s\n", has_restore_privilege?"Yes":"No"); + + status = smblsa_sid_check_privilege(cli, + owner_sid, + sec_privilege_name(SEC_PRIV_TAKE_OWNERSHIP)); + has_take_ownership_privilege = NT_STATUS_IS_OK(status); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smblsa_sid_check_privilege - %s\n", nt_errstr(status)); + } + torture_comment(tctx, "SEC_PRIV_TAKE_OWNERSHIP - %s\n", has_take_ownership_privilege?"Yes":"No"); +*/ + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + expected_bits = SEC_FILE_WRITE_DATA | SEC_FILE_READ_ATTRIBUTE; + + for (i=0;i<16;i++) { + uint32_t bit = (1<<i); + io.in.desired_access = bit; + status = smb2_create(tree, tctx, &io); + if (expected_bits & bit) { + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "failed with access mask 0x%08x of expected 0x%08x\n", + bit, expected_bits); + } + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, bit); + smb2_util_close(tree, io.out.file.handle); + } else { + if (NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "open succeeded with access mask 0x%08x of " + "expected 0x%08x - should fail\n", + bit, expected_bits); + } + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + } + + torture_comment(tctx, "put back original sd\n"); + set.set_secdesc.in.sd = sd_orig; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, handle); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + + + +/* + test the inheritance of ACL flags onto new files and directories + Note: This test was copied from raw/acls.c. +*/ +static bool test_inheritance(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *dname = BASEDIR "\\inheritance"; + const char *fname1 = BASEDIR "\\inheritance\\testfile"; + const char *fname2 = BASEDIR "\\inheritance\\testdir"; + bool ret = true; + struct smb2_handle handle = {{0}}; + struct smb2_handle handle2 = {{0}}; + int i; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd2, *sd_orig=NULL, *sd_def1, *sd_def2; + const char *owner_sid; + const struct dom_sid *creator_owner; + const struct { + uint32_t parent_flags; + uint32_t file_flags; + uint32_t dir_flags; + } test_flags[] = { + { + 0, + 0, + 0 + }, + { + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_INHERIT_ONLY, + }, + { + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + SEC_ACE_FLAG_CONTAINER_INHERIT, + }, + { + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_INHERIT_ONLY, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + SEC_ACE_FLAG_CONTAINER_INHERIT, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + 0, + 0, + }, + { + SEC_ACE_FLAG_INHERIT_ONLY | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_OBJECT_INHERIT, + 0, + 0, + } + }; + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + torture_comment(tctx, "TESTING ACL INHERITANCE\n"); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.share_access = 0; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = dname; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + torture_comment(tctx, "owner_sid is %s\n", owner_sid); + + /* + * The Windows Default ACL for a new file, when there is no ACL to be + * inherited: FullControl for the owner and SYSTEM. + */ + sd_def1 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_ALL, + 0, + SID_NT_SYSTEM, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_ALL, + 0, + NULL); + + /* + * Use this in the case the system being tested does not add an ACE for + * the SYSTEM SID. + */ + sd_def2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_ALL, + 0, + NULL); + + creator_owner = dom_sid_parse_talloc(tctx, SID_CREATOR_OWNER); + + for (i=0;i<ARRAY_SIZE(test_flags);i++) { + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA, + test_flags[i].parent_flags, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_ALL | SEC_STD_ALL, + 0, + NULL); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + io.in.fname = fname1; + io.in.create_options = 0; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + + q.query_secdesc.in.file.handle = handle2; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, handle2); + smb2_util_unlink(tree, fname1); + + if (!(test_flags[i].parent_flags & SEC_ACE_FLAG_OBJECT_INHERIT)) { + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd_def1) && + !security_descriptor_equal(q.query_secdesc.out.sd, sd_def2)) { + torture_warning(tctx, "Expected default sd:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd_def1); + torture_warning(tctx, "at %d - got:\n", i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + } + goto check_dir; + } + + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 1 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + sd_orig->owner_sid)) { + torture_warning(tctx, "Bad sd in child file at %d\n", i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + ret = false; + goto check_dir; + } + + if (q.query_secdesc.out.sd->dacl->aces[0].flags != + test_flags[i].file_flags) { + torture_warning(tctx, "incorrect file_flags 0x%x - expected 0x%x for parent 0x%x with (i=%d)\n", + q.query_secdesc.out.sd->dacl->aces[0].flags, + test_flags[i].file_flags, + test_flags[i].parent_flags, + i); + ret = false; + } + + check_dir: + io.in.fname = fname2; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + + q.query_secdesc.in.file.handle = handle2; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, handle2); + smb2_util_rmdir(tree, fname2); + + if (!(test_flags[i].parent_flags & SEC_ACE_FLAG_CONTAINER_INHERIT) && + (!(test_flags[i].parent_flags & SEC_ACE_FLAG_OBJECT_INHERIT) || + (test_flags[i].parent_flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT))) { + if (!security_descriptor_equal(q.query_secdesc.out.sd, sd_def1) && + !security_descriptor_equal(q.query_secdesc.out.sd, sd_def2)) { + torture_warning(tctx, "Expected default sd for dir at %d:\n", i); + NDR_PRINT_DEBUG(security_descriptor, sd_def1); + torture_warning(tctx, "got:\n"); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + } + continue; + } + + if ((test_flags[i].parent_flags & SEC_ACE_FLAG_CONTAINER_INHERIT) && + (test_flags[i].parent_flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT)) { + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 1 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + sd_orig->owner_sid) || + q.query_secdesc.out.sd->dacl->aces[0].flags != test_flags[i].dir_flags) { + torture_warning(tctx, "(CI & NP) Bad sd in child dir - expected 0x%x for parent 0x%x (i=%d)\n", + test_flags[i].dir_flags, + test_flags[i].parent_flags, i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + torture_warning(tctx, "FYI, here is the parent sd:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + continue; + } + } else if (test_flags[i].parent_flags & SEC_ACE_FLAG_CONTAINER_INHERIT) { + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 2 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + sd_orig->owner_sid) || + q.query_secdesc.out.sd->dacl->aces[1].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[1].trustee, + creator_owner) || + q.query_secdesc.out.sd->dacl->aces[0].flags != 0 || + q.query_secdesc.out.sd->dacl->aces[1].flags != + (test_flags[i].dir_flags | SEC_ACE_FLAG_INHERIT_ONLY)) { + torture_warning(tctx, "(CI) Bad sd in child dir - expected 0x%x for parent 0x%x (i=%d)\n", + test_flags[i].dir_flags, + test_flags[i].parent_flags, i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + torture_warning(tctx, "FYI, here is the parent sd:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + continue; + } + } else { + if (q.query_secdesc.out.sd->dacl == NULL || + q.query_secdesc.out.sd->dacl->num_aces != 1 || + q.query_secdesc.out.sd->dacl->aces[0].access_mask != SEC_FILE_WRITE_DATA || + !dom_sid_equal(&q.query_secdesc.out.sd->dacl->aces[0].trustee, + creator_owner) || + q.query_secdesc.out.sd->dacl->aces[0].flags != test_flags[i].dir_flags) { + torture_warning(tctx, "(0) Bad sd in child dir - expected 0x%x for parent 0x%x (i=%d)\n", + test_flags[i].dir_flags, + test_flags[i].parent_flags, i); + NDR_PRINT_DEBUG(security_descriptor, q.query_secdesc.out.sd); + torture_warning(tctx, "FYI, here is the parent sd:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + continue; + } + } + } + + torture_comment(tctx, "Testing access checks on inherited create with %s\n", fname1); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + SEC_ACE_FLAG_OBJECT_INHERIT, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_ALL | SEC_STD_ALL, + 0, + NULL); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Check DACL we just set. */ + torture_comment(tctx, "checking new sd\n"); + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd); + + io.in.fname = fname1; + io.in.create_options = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + CHECK_ACCESS_FLAGS(handle2, SEC_RIGHTS_FILE_ALL); + + q.query_secdesc.in.file.handle = handle2; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, handle2); + + sd2 = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + 0, + NULL); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + status = smb2_create(tree, tctx, &io); + if (NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "failed: w2k3 ACL bug (allowed open when ACL should deny)\n"); + ret = false; + handle2 = io.out.file.handle; + CHECK_ACCESS_FLAGS(handle2, SEC_RIGHTS_FILE_ALL); + smb2_util_close(tree, handle2); + } else { + if (torture_setting_bool(tctx, "hide_on_access_denied", + false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + } + + torture_comment(tctx, "trying without execute\n"); + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.desired_access = SEC_RIGHTS_FILE_ALL & ~SEC_FILE_EXECUTE; + status = smb2_create(tree, tctx, &io); + if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + torture_comment(tctx, "and with full permissions again\n"); + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + status = smb2_create(tree, tctx, &io); + if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + CHECK_ACCESS_FLAGS(handle2, SEC_FILE_WRITE_DATA); + smb2_util_close(tree, handle2); + + torture_comment(tctx, "put back original sd\n"); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd_orig; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, handle); + + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + status = smb2_create(tree, tctx, &io); + if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + CHECK_ACCESS_FLAGS(handle2, SEC_FILE_WRITE_DATA); + smb2_util_close(tree, handle2); + + smb2_util_unlink(tree, fname1); + smb2_util_rmdir(tree, dname); + +done: + if (sd_orig != NULL) { + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd_orig; + status = smb2_setinfo_file(tree, &set); + } + + smb2_util_close(tree, handle); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_inheritance_flags(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *dname = BASEDIR "\\inheritance"; + const char *fname1 = BASEDIR "\\inheritance\\testfile"; + bool ret = true; + struct smb2_handle handle = {{0}}; + struct smb2_handle handle2 = {{0}}; + int i, j; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd2, *sd_orig=NULL; + const char *owner_sid; + struct { + uint32_t parent_set_sd_type; /* 3 options */ + uint32_t parent_set_ace_inherit; /* 1 option */ + uint32_t parent_get_sd_type; + uint32_t parent_get_ace_inherit; + uint32_t child_get_sd_type; + uint32_t child_get_ace_inherit; + } tflags[16] = {{0}}; /* 2^4 */ + + for (i = 0; i < 15; i++) { + torture_comment(tctx, "i=%d:", i); + + if (i & 1) { + tflags[i].parent_set_sd_type |= + SEC_DESC_DACL_AUTO_INHERITED; + torture_comment(tctx, "AUTO_INHERITED, "); + } + if (i & 2) { + tflags[i].parent_set_sd_type |= + SEC_DESC_DACL_AUTO_INHERIT_REQ; + torture_comment(tctx, "AUTO_INHERIT_REQ, "); + } + if (i & 4) { + tflags[i].parent_set_sd_type |= + SEC_DESC_DACL_PROTECTED; + torture_comment(tctx, "PROTECTED, "); + tflags[i].parent_get_sd_type |= + SEC_DESC_DACL_PROTECTED; + } + if (i & 8) { + tflags[i].parent_set_ace_inherit |= + SEC_ACE_FLAG_INHERITED_ACE; + torture_comment(tctx, "INHERITED, "); + tflags[i].parent_get_ace_inherit |= + SEC_ACE_FLAG_INHERITED_ACE; + } + + if ((tflags[i].parent_set_sd_type & + (SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ)) == + (SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ)) { + tflags[i].parent_get_sd_type |= + SEC_DESC_DACL_AUTO_INHERITED; + tflags[i].child_get_sd_type |= + SEC_DESC_DACL_AUTO_INHERITED; + tflags[i].child_get_ace_inherit |= + SEC_ACE_FLAG_INHERITED_ACE; + torture_comment(tctx, " ... parent is AUTO INHERITED"); + } + + if (tflags[i].parent_set_ace_inherit & + SEC_ACE_FLAG_INHERITED_ACE) { + tflags[i].parent_get_ace_inherit = + SEC_ACE_FLAG_INHERITED_ACE; + torture_comment(tctx, " ... parent ACE is INHERITED"); + } + + torture_comment(tctx, "\n"); + } + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + torture_comment(tctx, "TESTING ACL INHERITANCE FLAGS\n"); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = dname; + + torture_comment(tctx, "creating initial directory %s\n", dname); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "getting original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + torture_comment(tctx, "owner_sid is %s\n", owner_sid); + + for (i=0; i < ARRAY_SIZE(tflags); i++) { + torture_comment(tctx, "setting a new sd on directory, pass #%d\n", i); + + sd = security_descriptor_dacl_create(tctx, + tflags[i].parent_set_sd_type, + NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT | + tflags[i].parent_set_ace_inherit, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_ALL | SEC_STD_ALL, + 0, + NULL); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Check DACL we just set, except change the bits to what they + * should be. + */ + torture_comment(tctx, " checking new sd\n"); + + /* REQ bit should always be false. */ + sd->type &= ~SEC_DESC_DACL_AUTO_INHERIT_REQ; + + if ((tflags[i].parent_get_sd_type & SEC_DESC_DACL_AUTO_INHERITED) == 0) + sd->type &= ~SEC_DESC_DACL_AUTO_INHERITED; + + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd); + + /* Create file. */ + torture_comment(tctx, " creating file %s\n", fname1); + io.in.fname = fname1; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + CHECK_ACCESS_FLAGS(handle2, SEC_RIGHTS_FILE_ALL); + + q.query_secdesc.in.file.handle = handle2; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, " checking sd on file %s\n", fname1); + sd2 = security_descriptor_dacl_create(tctx, + tflags[i].child_get_sd_type, + owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + tflags[i].child_get_ace_inherit, + NULL); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + /* + * Set new sd on file ... prove that the bits have nothing to + * do with the parents bits when manually setting an ACL. The + * _AUTO_INHERITED bit comes directly from the ACL set. + */ + for (j = 0; j < ARRAY_SIZE(tflags); j++) { + torture_comment(tctx, " setting new file sd, pass #%d\n", j); + + /* Change sd type. */ + sd2->type &= ~(SEC_DESC_DACL_AUTO_INHERITED | + SEC_DESC_DACL_AUTO_INHERIT_REQ | + SEC_DESC_DACL_PROTECTED); + sd2->type |= tflags[j].parent_set_sd_type; + + sd2->dacl->aces[0].flags &= + ~SEC_ACE_FLAG_INHERITED_ACE; + sd2->dacl->aces[0].flags |= + tflags[j].parent_set_ace_inherit; + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle2; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd2; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Check DACL we just set. */ + sd2->type &= ~SEC_DESC_DACL_AUTO_INHERIT_REQ; + if ((tflags[j].parent_get_sd_type & SEC_DESC_DACL_AUTO_INHERITED) == 0) + sd2->type &= ~SEC_DESC_DACL_AUTO_INHERITED; + + q.query_secdesc.in.file.handle = handle2; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + } + + smb2_util_close(tree, handle2); + smb2_util_unlink(tree, fname1); + } + +done: + smb2_util_close(tree, handle); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +/* + * This is basically a copy of test_inheritance_flags() with an additional twist + * to change the owner of the testfile, verifying that the security descriptor + * flags are not altered. + */ +static bool test_sd_flags_vs_chown(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *dname = BASEDIR "\\inheritance"; + const char *fname1 = BASEDIR "\\inheritance\\testfile"; + bool ret = true; + struct smb2_handle handle = {{0}}; + struct smb2_handle handle2 = {{0}}; + int i, j; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd2, *sd_orig=NULL; + struct security_descriptor *owner_sd = NULL; + const char *owner_sid_string = NULL; + struct dom_sid *owner_sid = NULL; + struct dom_sid world_sid = global_sid_World; + struct { + uint32_t parent_set_sd_type; /* 3 options */ + uint32_t parent_set_ace_inherit; /* 1 option */ + uint32_t parent_get_sd_type; + uint32_t parent_get_ace_inherit; + uint32_t child_get_sd_type; + uint32_t child_get_ace_inherit; + } tflags[16] = {{0}}; /* 2^4 */ + + owner_sd = security_descriptor_dacl_create(tctx, + 0, + SID_WORLD, + NULL, + NULL); + torture_assert_not_null_goto(tctx, owner_sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + for (i = 0; i < 15; i++) { + torture_comment(tctx, "i=%d:", i); + + if (i & 1) { + tflags[i].parent_set_sd_type |= + SEC_DESC_DACL_AUTO_INHERITED; + torture_comment(tctx, "AUTO_INHERITED, "); + } + if (i & 2) { + tflags[i].parent_set_sd_type |= + SEC_DESC_DACL_AUTO_INHERIT_REQ; + torture_comment(tctx, "AUTO_INHERIT_REQ, "); + } + if (i & 4) { + tflags[i].parent_set_sd_type |= + SEC_DESC_DACL_PROTECTED; + torture_comment(tctx, "PROTECTED, "); + tflags[i].parent_get_sd_type |= + SEC_DESC_DACL_PROTECTED; + } + if (i & 8) { + tflags[i].parent_set_ace_inherit |= + SEC_ACE_FLAG_INHERITED_ACE; + torture_comment(tctx, "INHERITED, "); + tflags[i].parent_get_ace_inherit |= + SEC_ACE_FLAG_INHERITED_ACE; + } + + if ((tflags[i].parent_set_sd_type & + (SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ)) == + (SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ)) { + tflags[i].parent_get_sd_type |= + SEC_DESC_DACL_AUTO_INHERITED; + tflags[i].child_get_sd_type |= + SEC_DESC_DACL_AUTO_INHERITED; + tflags[i].child_get_ace_inherit |= + SEC_ACE_FLAG_INHERITED_ACE; + torture_comment(tctx, " ... parent is AUTO INHERITED"); + } + + if (tflags[i].parent_set_ace_inherit & + SEC_ACE_FLAG_INHERITED_ACE) { + tflags[i].parent_get_ace_inherit = + SEC_ACE_FLAG_INHERITED_ACE; + torture_comment(tctx, " ... parent ACE is INHERITED"); + } + + torture_comment(tctx, "\n"); + } + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + torture_comment(tctx, "TESTING ACL INHERITANCE FLAGS\n"); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = dname; + + torture_comment(tctx, "creating initial directory %s\n", dname); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "getting original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = sd_orig->owner_sid; + owner_sid_string = dom_sid_string(tctx, sd_orig->owner_sid); + torture_comment(tctx, "owner_sid is %s\n", owner_sid_string); + + for (i=0; i < ARRAY_SIZE(tflags); i++) { + torture_comment(tctx, "setting a new sd on directory, pass #%d\n", i); + + sd = security_descriptor_dacl_create(tctx, + tflags[i].parent_set_sd_type, + NULL, NULL, + owner_sid_string, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT | + tflags[i].parent_set_ace_inherit, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_ALL | SEC_STD_ALL, + 0, + NULL); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Check DACL we just set, except change the bits to what they + * should be. + */ + torture_comment(tctx, " checking new sd\n"); + + /* REQ bit should always be false. */ + sd->type &= ~SEC_DESC_DACL_AUTO_INHERIT_REQ; + + if ((tflags[i].parent_get_sd_type & SEC_DESC_DACL_AUTO_INHERITED) == 0) + sd->type &= ~SEC_DESC_DACL_AUTO_INHERITED; + + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd); + + /* Create file. */ + torture_comment(tctx, " creating file %s\n", fname1); + io.in.fname = fname1; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + CHECK_ACCESS_FLAGS(handle2, SEC_RIGHTS_FILE_ALL); + + q.query_secdesc.in.file.handle = handle2; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, " checking sd on file %s\n", fname1); + sd2 = security_descriptor_dacl_create(tctx, + tflags[i].child_get_sd_type, + owner_sid_string, NULL, + owner_sid_string, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_WRITE_DAC, + tflags[i].child_get_ace_inherit, + NULL); + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + /* + * Set new sd on file ... prove that the bits have nothing to + * do with the parents bits when manually setting an ACL. The + * _AUTO_INHERITED bit comes directly from the ACL set. + */ + for (j = 0; j < ARRAY_SIZE(tflags); j++) { + torture_comment(tctx, " setting new file sd, pass #%d\n", j); + + /* Change sd type. */ + sd2->type &= ~(SEC_DESC_DACL_AUTO_INHERITED | + SEC_DESC_DACL_AUTO_INHERIT_REQ | + SEC_DESC_DACL_PROTECTED); + sd2->type |= tflags[j].parent_set_sd_type; + + sd2->dacl->aces[0].flags &= + ~SEC_ACE_FLAG_INHERITED_ACE; + sd2->dacl->aces[0].flags |= + tflags[j].parent_set_ace_inherit; + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle2; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd2; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Check DACL we just set. */ + sd2->type &= ~SEC_DESC_DACL_AUTO_INHERIT_REQ; + if ((tflags[j].parent_get_sd_type & SEC_DESC_DACL_AUTO_INHERITED) == 0) + sd2->type &= ~SEC_DESC_DACL_AUTO_INHERITED; + + q.query_secdesc.in.file.handle = handle2; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + + /* + * Check that changing ownder doesn't affect SD flags. + * + * Do this by first changing ownder to world and then + * back to the original ownder. Afterwards compare SD, + * should be the same. + */ + owner_sd->owner_sid = &world_sid; + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle2; + set.set_secdesc.in.secinfo_flags = SECINFO_OWNER; + set.set_secdesc.in.sd = owner_sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + owner_sd->owner_sid = owner_sid; + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle2; + set.set_secdesc.in.secinfo_flags = SECINFO_OWNER; + set.set_secdesc.in.sd = owner_sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + q.query_secdesc.in.file.handle = handle2; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_SECURITY_DESCRIPTOR(q.query_secdesc.out.sd, sd2); + torture_assert_goto(tctx, ret, ret, done, "CHECK_SECURITY_DESCRIPTOR failed\n"); + } + + smb2_util_close(tree, handle2); + smb2_util_unlink(tree, fname1); + } + +done: + smb2_util_close(tree, handle); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +/* + test dynamic acl inheritance + Note: This test was copied from raw/acls.c. +*/ +static bool test_inheritance_dynamic(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *dname = BASEDIR "\\inheritance"; + const char *fname1 = BASEDIR "\\inheritance\\testfile"; + bool ret = true; + struct smb2_handle handle = {{0}}; + struct smb2_handle handle2 = {{0}}; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig=NULL; + const char *owner_sid; + + torture_comment(tctx, "TESTING DYNAMIC ACL INHERITANCE\n"); + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.share_access = 0; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = dname; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + torture_comment(tctx, "owner_sid is %s\n", owner_sid); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_DELETE | SEC_FILE_READ_ATTRIBUTE, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + sd->type |= SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ; + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "create a file with an inherited acl\n"); + io.in.fname = fname1; + io.in.create_options = 0; + io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + smb2_util_close(tree, handle2); + + torture_comment(tctx, "try and access file with base rights - should be OK\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + smb2_util_close(tree, handle2); + + torture_comment(tctx, "try and access file with extra rights - should be denied\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA | SEC_FILE_EXECUTE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(tctx, "update parent sd\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA | SEC_STD_DELETE | SEC_FILE_READ_ATTRIBUTE | SEC_FILE_EXECUTE, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + sd->type |= SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_DACL_AUTO_INHERIT_REQ; + + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "try and access file with base rights - should be OK\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle2 = io.out.file.handle; + smb2_util_close(tree, handle2); + + + torture_comment(tctx, "try and access now - should be OK if dynamic inheritance works\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA | SEC_FILE_EXECUTE; + status = smb2_create(tree, tctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, "Server does not have dynamic inheritance\n"); + } + if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(tctx, "Server does have dynamic inheritance\n"); + } + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + smb2_util_unlink(tree, fname1); + +done: + torture_comment(tctx, "put back original sd\n"); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd_orig; + status = smb2_setinfo_file(tree, &set); + + smb2_util_close(tree, handle); + smb2_util_rmdir(tree, dname); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + + return ret; +} + +#define CHECK_STATUS_FOR_BIT_ACTION(status, bits, action) do { \ + if (!(bits & desired_64)) {\ + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); \ + action; \ + } else { \ + CHECK_STATUS(status, NT_STATUS_OK); \ + } \ +} while (0) + +#define CHECK_STATUS_FOR_BIT(status, bits, access) do { \ + if (NT_STATUS_IS_OK(status)) { \ + if (!(granted & access)) {\ + ret = false; \ + torture_result(tctx, TORTURE_FAIL, "(%s) %s but flags 0x%08X are not granted! granted[0x%08X] desired[0x%08X]\n", \ + __location__, nt_errstr(status), access, granted, desired); \ + goto done; \ + } \ + } else { \ + if (granted & access) {\ + ret = false; \ + torture_result(tctx, TORTURE_FAIL, "(%s) %s but flags 0x%08X are granted! granted[0x%08X] desired[0x%08X]\n", \ + __location__, nt_errstr(status), access, granted, desired); \ + goto done; \ + } \ + } \ + CHECK_STATUS_FOR_BIT_ACTION(status, bits, do {} while (0)); \ +} while (0) + +#if 0 +/* test what access mask is needed for getting and setting security_descriptors */ +/* Note: This test was copied from raw/acls.c. */ +static bool test_sd_get_set(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_create io; + union smb_fileinfo fi; + union smb_setfileinfo si; + struct security_descriptor *sd; + struct security_descriptor *sd_owner = NULL; + struct security_descriptor *sd_group = NULL; + struct security_descriptor *sd_dacl = NULL; + struct security_descriptor *sd_sacl = NULL; + struct smb2_handle handle; + const char *fname = BASEDIR "\\sd_get_set.txt"; + uint64_t desired_64; + uint32_t desired = 0, granted; + int i = 0; +#define NO_BITS_HACK (((uint64_t)1)<<32) + uint64_t open_bits = + SEC_MASK_GENERIC | + SEC_FLAG_SYSTEM_SECURITY | + SEC_FLAG_MAXIMUM_ALLOWED | + SEC_STD_ALL | + SEC_FILE_ALL | + NO_BITS_HACK; + uint64_t get_owner_bits = SEC_MASK_GENERIC | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_READ_CONTROL; + uint64_t set_owner_bits = SEC_GENERIC_ALL | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_WRITE_OWNER; + uint64_t get_group_bits = SEC_MASK_GENERIC | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_READ_CONTROL; + uint64_t set_group_bits = SEC_GENERIC_ALL | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_WRITE_OWNER; + uint64_t get_dacl_bits = SEC_MASK_GENERIC | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_READ_CONTROL; + uint64_t set_dacl_bits = SEC_GENERIC_ALL | SEC_FLAG_MAXIMUM_ALLOWED | SEC_STD_WRITE_DAC; + uint64_t get_sacl_bits = SEC_FLAG_SYSTEM_SECURITY; + uint64_t set_sacl_bits = SEC_FLAG_SYSTEM_SECURITY; + + if (!smb2_util_setup_dir(tctx, tree, BASEDIR)) + return false; + + torture_comment(tctx, "TESTING ACCESS MASKS FOR SD GET/SET\n"); + + /* first create a file with full access for everyone */ + sd = security_descriptor_dacl_create(tctx, + 0, SID_NT_ANONYMOUS, SID_BUILTIN_USERS, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + 0, + NULL); + sd->type |= SEC_DESC_SACL_PRESENT; + sd->sacl = NULL; + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_GENERIC_ALL; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname; + io.in.sec_desc = sd; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + status = smb2_util_close(tree, handle); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * now try each access_mask bit and no bit at all in a loop + * and see what's allowed + * NOTE: if i == 32 it means access_mask = 0 (see NO_BITS_HACK above) + */ + for (i=0; i <= 32; i++) { + desired_64 = ((uint64_t)1) << i; + desired = (uint32_t)desired_64; + + /* first open the file with the desired access */ + io.level = RAW_OPEN_SMB2; + io.in.desired_access = desired; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS_FOR_BIT_ACTION(status, open_bits, goto next); + handle = io.out.file.handle; + + /* then check what access was granted */ + fi.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; + fi.access_information.in.file.handle = handle; + status = smb2_getinfo_file(tree, tctx, &fi); + CHECK_STATUS(status, NT_STATUS_OK); + granted = fi.access_information.out.access_flags; + + /* test the owner */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.handle = handle; + fi.query_secdesc.in.secinfo_flags = SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_owner_bits, SEC_STD_READ_CONTROL); + if (fi.query_secdesc.out.sd) { + sd_owner = fi.query_secdesc.out.sd; + } else if (!sd_owner) { + sd_owner = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.handle = handle; + si.set_secdesc.in.secinfo_flags = SECINFO_OWNER; + si.set_secdesc.in.sd = sd_owner; + status = smb2_setinfo_file(tree, &si); + CHECK_STATUS_FOR_BIT(status, set_owner_bits, SEC_STD_WRITE_OWNER); + + /* test the group */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.handle = handle; + fi.query_secdesc.in.secinfo_flags = SECINFO_GROUP; + status = smb2_getinfo_file(tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_group_bits, SEC_STD_READ_CONTROL); + if (fi.query_secdesc.out.sd) { + sd_group = fi.query_secdesc.out.sd; + } else if (!sd_group) { + sd_group = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.handle = handle; + si.set_secdesc.in.secinfo_flags = SECINFO_GROUP; + si.set_secdesc.in.sd = sd_group; + status = smb2_setinfo_file(tree, &si); + CHECK_STATUS_FOR_BIT(status, set_group_bits, SEC_STD_WRITE_OWNER); + + /* test the DACL */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.handle = handle; + fi.query_secdesc.in.secinfo_flags = SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_dacl_bits, SEC_STD_READ_CONTROL); + if (fi.query_secdesc.out.sd) { + sd_dacl = fi.query_secdesc.out.sd; + } else if (!sd_dacl) { + sd_dacl = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.handle = handle; + si.set_secdesc.in.secinfo_flags = SECINFO_DACL; + si.set_secdesc.in.sd = sd_dacl; + status = smb2_setinfo_file(tree, &si); + CHECK_STATUS_FOR_BIT(status, set_dacl_bits, SEC_STD_WRITE_DAC); + + /* test the SACL */ + ZERO_STRUCT(fi); + fi.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + fi.query_secdesc.in.file.handle = handle; + fi.query_secdesc.in.secinfo_flags = SECINFO_SACL; + status = smb2_getinfo_file(tree, tctx, &fi); + CHECK_STATUS_FOR_BIT(status, get_sacl_bits, SEC_FLAG_SYSTEM_SECURITY); + if (fi.query_secdesc.out.sd) { + sd_sacl = fi.query_secdesc.out.sd; + } else if (!sd_sacl) { + sd_sacl = sd; + } + si.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + si.set_secdesc.in.file.handle = handle; + si.set_secdesc.in.secinfo_flags = SECINFO_SACL; + si.set_secdesc.in.sd = sd_sacl; + status = smb2_setinfo_file(tree, &si); + CHECK_STATUS_FOR_BIT(status, set_sacl_bits, SEC_FLAG_SYSTEM_SECURITY); + + /* close the handle */ + status = smb2_util_close(tree, handle); + CHECK_STATUS(status, NT_STATUS_OK); +next: + continue; + } + +done: + smb2_util_close(tree, handle); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, BASEDIR); + smb2_tdis(tree); + smb2_logoff(tree->session); + + return ret; +} +#endif + +static bool test_access_based(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_tree *tree1 = NULL; + NTSTATUS status; + struct smb2_create io; + const char *fname = BASEDIR "\\testfile"; + bool ret = true; + struct smb2_handle fhandle, dhandle; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig=NULL; + const char *owner_sid; + uint32_t flags = 0; + /* + * Can't test without SEC_STD_READ_CONTROL as we + * own the file and implicitly have SEC_STD_READ_CONTROL. + */ + uint32_t access_masks[] = { + /* Full READ access. */ + SEC_STD_READ_CONTROL|FILE_READ_DATA| + FILE_READ_ATTRIBUTES|FILE_READ_EA, + + /* Missing FILE_READ_EA. */ + SEC_STD_READ_CONTROL|FILE_READ_DATA| + FILE_READ_ATTRIBUTES, + + /* Missing FILE_READ_ATTRIBUTES. */ + SEC_STD_READ_CONTROL|FILE_READ_DATA| + FILE_READ_EA, + + /* Missing FILE_READ_DATA. */ + SEC_STD_READ_CONTROL| + FILE_READ_ATTRIBUTES|FILE_READ_EA, + }; + unsigned int i; + unsigned int count; + struct smb2_find f; + union smb_search_data *d; + + ZERO_STRUCT(fhandle); + ZERO_STRUCT(dhandle); + + if (!torture_smb2_con_share(tctx, "hideunread", &tree1)) { + torture_result(tctx, TORTURE_FAIL, "(%s) Unable to connect " + "to share 'hideunread'\n", + __location__); + ret = false; + goto done; + } + + flags = smb2cli_tcon_flags(tree1->smbXcli); + + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, BASEDIR); + + torture_comment(tctx, "TESTING ACCESS BASED ENUMERATION\n"); + + if ((flags & SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM)==0) { + torture_result(tctx, TORTURE_FAIL, "(%s) No access enumeration " + "on share 'hideunread'\n", + __location__); + ret = false; + goto done; + } + + if (!smb2_util_setup_dir(tctx, tree1, BASEDIR)) { + torture_result(tctx, TORTURE_FAIL, "(%s) Unable to setup %s\n", + __location__, BASEDIR); + ret = false; + goto done; + } + + /* Get a handle to the BASEDIR directory. */ + status = torture_smb2_testdir(tree1, BASEDIR, &dhandle); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, dhandle); + ZERO_STRUCT(dhandle); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = 0; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname; + + status = smb2_create(tree1, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + fhandle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = fhandle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree1, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + torture_comment(tctx, "owner_sid is %s\n", owner_sid); + + /* Setup for the search. */ + ZERO_STRUCT(f); + f.in.pattern = "*"; + f.in.continue_flags = SMB2_CONTINUE_FLAG_REOPEN; + f.in.max_response_size = 0x1000; + f.in.level = SMB2_FIND_DIRECTORY_INFO; + + for (i = 0; i < ARRAY_SIZE(access_masks); i++) { + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + access_masks[i]|SEC_STD_SYNCHRONIZE, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = fhandle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree1, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Now see if we can see the file in a directory listing. */ + + /* Re-open dhandle. */ + status = torture_smb2_testdir(tree1, BASEDIR, &dhandle); + CHECK_STATUS(status, NT_STATUS_OK); + f.in.file.handle = dhandle; + + count = 0; + d = NULL; + status = smb2_find_level(tree1, tree1, &f, &count, &d); + TALLOC_FREE(d); + + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree1, dhandle); + ZERO_STRUCT(dhandle); + + if (i == 0) { + /* We should see the first sd. */ + if (count != 3) { + torture_result(tctx, TORTURE_FAIL, + "(%s) Normal SD - Unable " + "to see file %s\n", + __location__, + BASEDIR); + ret = false; + goto done; + } + } else { + /* But no others. */ + if (count != 2) { + torture_result(tctx, TORTURE_FAIL, + "(%s) SD 0x%x - can " + "see file %s\n", + __location__, + access_masks[i], + BASEDIR); + ret = false; + goto done; + } + } + } + +done: + + if (tree1) { + smb2_util_close(tree1, fhandle); + smb2_util_close(tree1, dhandle); + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, BASEDIR); + smb2_tdis(tree1); + smb2_logoff(tree1->session); + } + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +/* + * test Owner Rights, S-1-3-4 + */ +static bool test_owner_rights(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = BASEDIR "\\owner_right.txt"; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + union smb_fileinfo gi; + union smb_setfileinfo si; + struct security_descriptor *sd_orig = NULL; + struct security_descriptor *sd = NULL; + const char *owner_sid = NULL; + NTSTATUS mxac_status; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, + "smb2_util_setup_dir failed\n"); + + torture_comment(tctx, "TESTING OWNER RIGHTS\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC |SEC_STD_WRITE_OWNER, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = handle, + .query_secdesc.in.secinfo_flags = SECINFO_DACL|SECINFO_OWNER, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + sd_orig = gi.query_secdesc.out.sd; + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + /* + * Add a 2 element ACL + * SEC_RIGHTS_FILE_READ for the owner, + * SEC_FILE_WRITE_DATA for SID_OWNER_RIGHTS. + * + * Proves that the owner and SID_OWNER_RIGHTS + * ACE entries are additive. + */ + sd = security_descriptor_dacl_create(tctx, 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ, + 0, + SID_OWNER_RIGHTS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "SD create failed\n"); + + si = (union smb_setfileinfo) { + .set_secdesc.level = RAW_SFILEINFO_SEC_DESC, + .set_secdesc.in.file.handle = handle, + .set_secdesc.in.secinfo_flags = SECINFO_DACL, + .set_secdesc.in.sd = sd, + }; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.query_maximal_access = true, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + handle = cr.out.file.handle; + + mxac_status = NT_STATUS(cr.out.maximal_access_status); + torture_assert_ntstatus_ok_goto(tctx, mxac_status, ret, done, + "smb2_setinfo_file failed\n"); + + /* + * For some reasons Windows 2016 doesn't set SEC_STD_DELETE but we + * do. Mask it out so the test passes against Samba and Windows. + */ + torture_assert_int_equal_goto(tctx, + cr.out.maximal_access & ~SEC_STD_DELETE, + SEC_RIGHTS_FILE_READ | + SEC_FILE_WRITE_DATA, + ret, done, + "Wrong maximum access\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + * test Owner Rights with a leading DENY ACE, S-1-3-4 + */ +static bool test_owner_rights_deny(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = BASEDIR "\\owner_right_deny.txt"; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + union smb_fileinfo gi; + union smb_setfileinfo si; + struct security_descriptor *sd_orig = NULL; + struct security_descriptor *sd = NULL; + const char *owner_sid = NULL; + NTSTATUS mxac_status; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, + "smb2_util_setup_dir failed\n"); + + torture_comment(tctx, "TESTING OWNER RIGHTS DENY\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC |SEC_STD_WRITE_OWNER, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = handle, + .query_secdesc.in.secinfo_flags = SECINFO_DACL|SECINFO_OWNER, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + sd_orig = gi.query_secdesc.out.sd; + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + /* + * Add a 2 element ACL + * DENY SEC_FILE_DATA_READ for SID_OWNER_RIGHTS + * SEC_FILE_READ_DATA for the owner. + * + * Proves that the owner and SID_OWNER_RIGHTS + * ACE entries are additive. + */ + sd = security_descriptor_dacl_create(tctx, 0, NULL, NULL, + SID_OWNER_RIGHTS, + SEC_ACE_TYPE_ACCESS_DENIED, + SEC_FILE_READ_DATA, + 0, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "SD create failed\n"); + + si = (union smb_setfileinfo) { + .set_secdesc.level = RAW_SFILEINFO_SEC_DESC, + .set_secdesc.in.file.handle = handle, + .set_secdesc.in.secinfo_flags = SECINFO_DACL, + .set_secdesc.in.sd = sd, + }; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.query_maximal_access = true, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + handle = cr.out.file.handle; + + mxac_status = NT_STATUS(cr.out.maximal_access_status); + torture_assert_ntstatus_ok_goto(tctx, mxac_status, ret, done, + "smb2_setinfo_file failed\n"); + + /* + * For some reasons Windows 2016 doesn't set SEC_STD_DELETE but we + * do. Mask it out so the test passes against Samba and Windows. + */ + torture_assert_int_equal_goto(tctx, + cr.out.maximal_access & ~SEC_STD_DELETE, + SEC_RIGHTS_FILE_READ & ~SEC_FILE_READ_DATA, + ret, done, + "Wrong maximum access\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + * test Owner Rights with a trailing DENY ACE, S-1-3-4 + */ +static bool test_owner_rights_deny1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = BASEDIR "\\owner_right_deny1.txt"; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + union smb_fileinfo gi; + union smb_setfileinfo si; + struct security_descriptor *sd_orig = NULL; + struct security_descriptor *sd = NULL; + const char *owner_sid = NULL; + NTSTATUS mxac_status; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, + "smb2_util_setup_dir failed\n"); + + torture_comment(tctx, "TESTING OWNER RIGHTS DENY1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC |SEC_STD_WRITE_OWNER, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = handle, + .query_secdesc.in.secinfo_flags = SECINFO_DACL|SECINFO_OWNER, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + sd_orig = gi.query_secdesc.out.sd; + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + /* + * Add a 3 element ACL + * + * SEC_RIGHTS_FILE_READ allow for owner. + * SEC_FILE_WRITE_DATA allow for SID-OWNER-RIGHTS. + * SEC_FILE_WRITE_DATA|SEC_FILE_READ_DATA) deny for SID-OWNER-RIGHTS. + * + * Shows on Windows that trailing DENY entries don't + * override granted permissions in max access calculations. + */ + sd = security_descriptor_dacl_create(tctx, 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ, + 0, + SID_OWNER_RIGHTS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_WRITE_DATA, + 0, + SID_OWNER_RIGHTS, + SEC_ACE_TYPE_ACCESS_DENIED, + (SEC_FILE_WRITE_DATA| + SEC_FILE_READ_DATA), + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "SD create failed\n"); + + si = (union smb_setfileinfo) { + .set_secdesc.level = RAW_SFILEINFO_SEC_DESC, + .set_secdesc.in.file.handle = handle, + .set_secdesc.in.secinfo_flags = SECINFO_DACL, + .set_secdesc.in.sd = sd, + }; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.query_maximal_access = true, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + handle = cr.out.file.handle; + + mxac_status = NT_STATUS(cr.out.maximal_access_status); + torture_assert_ntstatus_ok_goto(tctx, mxac_status, ret, done, + "smb2_setinfo_file failed\n"); + + /* + * For some reasons Windows 2016 doesn't set SEC_STD_DELETE but we + * do. Mask it out so the test passes against Samba and Windows. + */ + torture_assert_int_equal_goto(tctx, + cr.out.maximal_access & ~SEC_STD_DELETE, + SEC_RIGHTS_FILE_READ | SEC_FILE_WRITE_DATA, + ret, done, + "Wrong maximum access\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + * test that shows that a DENY ACE doesn't remove rights granted + * by a previous ALLOW ACE. + */ +static bool test_deny1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = BASEDIR "\\test_deny1.txt"; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + union smb_fileinfo gi; + union smb_setfileinfo si; + struct security_descriptor *sd_orig = NULL; + struct security_descriptor *sd = NULL; + const char *owner_sid = NULL; + NTSTATUS mxac_status; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, + "smb2_util_setup_dir failed\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC |SEC_STD_WRITE_OWNER, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = handle, + .query_secdesc.in.secinfo_flags = SECINFO_DACL|SECINFO_OWNER, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + sd_orig = gi.query_secdesc.out.sd; + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + /* + * Add a 2 element ACL + * + * SEC_RIGHTS_FILE_READ|SEC_FILE_WRITE_DATA allow for owner. + * SEC_FILE_WRITE_DATA deny for owner + * + * Shows on Windows that trailing DENY entries don't + * override granted permissions. + */ + sd = security_descriptor_dacl_create(tctx, 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ|SEC_FILE_WRITE_DATA, + 0, + owner_sid, + SEC_ACE_TYPE_ACCESS_DENIED, + SEC_FILE_WRITE_DATA, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "SD create failed\n"); + + si = (union smb_setfileinfo) { + .set_secdesc.level = RAW_SFILEINFO_SEC_DESC, + .set_secdesc.in.file.handle = handle, + .set_secdesc.in.secinfo_flags = SECINFO_DACL, + .set_secdesc.in.sd = sd, + }; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | SEC_FILE_WRITE_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.query_maximal_access = true, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + mxac_status = NT_STATUS(cr.out.maximal_access_status); + torture_assert_ntstatus_ok_goto(tctx, mxac_status, ret, done, + "Wrong maximum access status\n"); + + /* + * For some reasons Windows 2016 doesn't set SEC_STD_DELETE but we + * do. Mask it out so the test passes against Samba and Windows. + * SEC_STD_WRITE_DAC comes from being the owner. + */ + torture_assert_int_equal_goto(tctx, + cr.out.maximal_access & ~SEC_STD_DELETE, + SEC_RIGHTS_FILE_READ | + SEC_FILE_WRITE_DATA | + SEC_STD_WRITE_DAC, + ret, done, + "Wrong maximum access\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + * test SEC_FLAG_MAXIMUM_ALLOWED with not-granted access + * + * When access_mask contains SEC_FLAG_MAXIMUM_ALLOWED, the server must still + * proces other bits from access_mask. Eg if access_mask contains a right that + * the requester doesn't have, the function must validate that against the + * effective permissions. + */ +static bool test_mxac_not_granted(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = BASEDIR "\\test_mxac_not_granted.txt"; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + union smb_fileinfo gi; + union smb_setfileinfo si; + struct security_descriptor *sd_orig = NULL; + struct security_descriptor *sd = NULL; + const char *owner_sid = NULL; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, + "smb2_util_setup_dir failed\n"); + + torture_comment(tctx, "TESTING OWNER RIGHTS DENY\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC |SEC_STD_WRITE_OWNER, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = handle, + .query_secdesc.in.secinfo_flags = SECINFO_DACL|SECINFO_OWNER, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + sd_orig = gi.query_secdesc.out.sd; + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + sd = security_descriptor_dacl_create(tctx, 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_READ_DATA, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "SD create failed\n"); + + si = (union smb_setfileinfo) { + .set_secdesc.level = RAW_SFILEINFO_SEC_DESC, + .set_secdesc.in.file.handle = handle, + .set_secdesc.in.secinfo_flags = SECINFO_DACL, + .set_secdesc.in.sd = sd, + }; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED | + SEC_FILE_WRITE_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_ACCESS_DENIED, + ret, done, + "Wrong smb2_create result\n"); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_overwrite_read_only_file(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create c; + const char *fname = BASEDIR "\\test_overwrite_read_only_file.txt"; + struct smb2_handle handle = {{0}}; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd = NULL, *sd_orig = NULL; + const char *owner_sid = NULL; + int i; + bool ret = true; + + struct tcase { + int disposition; + const char *disposition_string; + NTSTATUS expected_status; + } tcases[] = { +#define TCASE(d, s) { \ + .disposition = d, \ + .disposition_string = #d, \ + .expected_status = s, \ + } + TCASE(NTCREATEX_DISP_OPEN, NT_STATUS_OK), + TCASE(NTCREATEX_DISP_SUPERSEDE, NT_STATUS_ACCESS_DENIED), + TCASE(NTCREATEX_DISP_OVERWRITE, NT_STATUS_ACCESS_DENIED), + TCASE(NTCREATEX_DISP_OVERWRITE_IF, NT_STATUS_ACCESS_DENIED), + }; +#undef TCASE + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, "smb2_util_setup_dir not ok"); + + c = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = c.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + + ZERO_STRUCT(q); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + + status = smb2_getinfo_file(tree, tctx, &q); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_READ_DATA, + 0, + NULL); + + ZERO_STRUCT(set); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + smb2_util_close(tree, handle); + ZERO_STRUCT(handle); + + for (i = 0; i < ARRAY_SIZE(tcases); i++) { + torture_comment(tctx, "Verify open with %s dispostion\n", + tcases[i].disposition_string); + + c = (struct smb2_create) { + .in.create_disposition = tcases[i].disposition, + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &c); + smb2_util_close(tree, c.out.file.handle); + torture_assert_ntstatus_equal_goto( + tctx, status, tcases[i].expected_status, ret, done, + "smb2_create failed\n"); + }; + + torture_comment(tctx, "put back original sd\n"); + + c = (struct smb2_create) { + .in.desired_access = SEC_STD_WRITE_DAC, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = c.out.file.handle; + + ZERO_STRUCT(set); + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd_orig; + + status = smb2_setinfo_file(tree, &set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + smb2_util_close(tree, handle); + ZERO_STRUCT(handle); + +done: + smb2_util_close(tree, handle); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + basic testing of SMB2 ACLs +*/ +struct torture_suite *torture_smb2_acls_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "acls"); + + torture_suite_add_1smb2_test(suite, "CREATOR", test_creator_sid); + torture_suite_add_1smb2_test(suite, "GENERIC", test_generic_bits); + torture_suite_add_1smb2_test(suite, "OWNER", test_owner_bits); + torture_suite_add_1smb2_test(suite, "INHERITANCE", test_inheritance); + torture_suite_add_1smb2_test(suite, "INHERITFLAGS", test_inheritance_flags); + torture_suite_add_1smb2_test(suite, "SDFLAGSVSCHOWN", test_sd_flags_vs_chown); + torture_suite_add_1smb2_test(suite, "DYNAMIC", test_inheritance_dynamic); +#if 0 + /* XXX This test does not work against XP or Vista. */ + torture_suite_add_1smb2_test(suite, "GETSET", test_sd_get_set); +#endif + torture_suite_add_1smb2_test(suite, "ACCESSBASED", test_access_based); + torture_suite_add_1smb2_test(suite, "OWNER-RIGHTS", test_owner_rights); + torture_suite_add_1smb2_test(suite, "OWNER-RIGHTS-DENY", + test_owner_rights_deny); + torture_suite_add_1smb2_test(suite, "OWNER-RIGHTS-DENY1", + test_owner_rights_deny1); + torture_suite_add_1smb2_test(suite, "DENY1", + test_deny1); + torture_suite_add_1smb2_test(suite, "MXAC-NOT-GRANTED", + test_mxac_not_granted); + torture_suite_add_1smb2_test(suite, "OVERWRITE_READ_ONLY_FILE", test_overwrite_read_only_file); + + suite->description = talloc_strdup(suite, "SMB2-ACLS tests"); + + return suite; +} + +static bool test_acls_non_canonical_flags(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = BASEDIR "\\test_acls_non_canonical_flags.txt"; + struct smb2_create cr; + struct smb2_handle testdirh = {{0}}; + struct smb2_handle handle = {{0}}; + union smb_fileinfo gi; + union smb_setfileinfo si; + struct security_descriptor *sd_orig = NULL; + struct security_descriptor *sd = NULL; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + status = torture_smb2_testdir(tree, BASEDIR, &testdirh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + + sd = security_descriptor_dacl_create(tctx, + SEC_DESC_DACL_AUTO_INHERITED + | SEC_DESC_DACL_AUTO_INHERIT_REQ, + NULL, + NULL, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_DIR_ALL, + SEC_ACE_FLAG_OBJECT_INHERIT + | SEC_ACE_FLAG_CONTAINER_INHERIT, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "SD create failed\n"); + + si = (union smb_setfileinfo) { + .set_secdesc.level = RAW_SFILEINFO_SEC_DESC, + .set_secdesc.in.file.handle = testdirh, + .set_secdesc.in.secinfo_flags = SECINFO_DACL, + .set_secdesc.in.sd = sd, + }; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = testdirh, + .query_secdesc.in.secinfo_flags = SECINFO_DACL, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = handle, + .query_secdesc.in.secinfo_flags = SECINFO_DACL, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + sd_orig = gi.query_secdesc.out.sd; + + torture_assert_goto(tctx, sd_orig->type & SEC_DESC_DACL_AUTO_INHERITED, + ret, done, "Missing SEC_DESC_DACL_AUTO_INHERITED\n"); + + /* + * SD with SEC_DESC_DACL_AUTO_INHERITED but without + * SEC_DESC_DACL_AUTO_INHERITED_REQ, so the resulting SD should not have + * SEC_DESC_DACL_AUTO_INHERITED on a Windows box. + * + * But as we're testing against a share with + * + * "acl flag inherited canonicalization = no" + * + * the resulting SD should have acl flag inherited canonicalization set. + */ + sd = security_descriptor_dacl_create(tctx, + SEC_DESC_DACL_AUTO_INHERITED, + NULL, + NULL, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_FILE_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "SD create failed\n"); + + si = (union smb_setfileinfo) { + .set_secdesc.level = RAW_SFILEINFO_SEC_DESC, + .set_secdesc.in.file.handle = handle, + .set_secdesc.in.secinfo_flags = SECINFO_DACL, + .set_secdesc.in.sd = sd, + }; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED , + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + gi = (union smb_fileinfo) { + .query_secdesc.level = RAW_FILEINFO_SEC_DESC, + .query_secdesc.in.file.handle = handle, + .query_secdesc.in.secinfo_flags = SECINFO_DACL, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + sd_orig = gi.query_secdesc.out.sd; + torture_assert_goto(tctx, sd_orig->type & SEC_DESC_DACL_AUTO_INHERITED, + ret, done, "Missing SEC_DESC_DACL_AUTO_INHERITED\n"); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, testdirh); + } + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +struct torture_suite *torture_smb2_acls_non_canonical_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "acls_non_canonical"); + + torture_suite_add_1smb2_test(suite, "flags", test_acls_non_canonical_flags); + return suite; +} diff --git a/source4/torture/smb2/attr.c b/source4/torture/smb2/attr.c new file mode 100644 index 0000000..6006897 --- /dev/null +++ b/source4/torture/smb2/attr.c @@ -0,0 +1,653 @@ +/* + Unix SMB/CIFS implementation. + + openattr tester + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) David Mulder 2019 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "libcli/security/security_descriptor.h" +#include "torture/smb2/proto.h" + +static const uint32_t open_attrs_table[] = { + FILE_ATTRIBUTE_NORMAL, + FILE_ATTRIBUTE_ARCHIVE, + FILE_ATTRIBUTE_READONLY, + FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_SYSTEM, + + FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY, + FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, + FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, + FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM, + + FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, + FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM, + FILE_ATTRIBUTE_HIDDEN,FILE_ATTRIBUTE_SYSTEM, +}; + +struct trunc_open_results { + unsigned int num; + uint32_t init_attr; + uint32_t trunc_attr; + uint32_t result_attr; +}; + +static const struct trunc_open_results attr_results[] = { + { 0, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_ARCHIVE }, + { 1, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_ARCHIVE }, + { 2, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY }, + { 16, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_ARCHIVE }, + { 17, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_ARCHIVE }, + { 18, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY }, + { 51, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN }, + { 54, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN }, + { 56, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN }, + { 68, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM }, + { 71, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM }, + { 73, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM }, + { 99, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN,FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN }, + { 102, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN }, + { 104, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN }, + { 116, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM }, + { 119, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM }, + { 121, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM }, + { 170, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN }, + { 173, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM }, + { 227, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN }, + { 230, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN }, + { 232, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN }, + { 244, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM }, + { 247, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM }, + { 249, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM } +}; + +static NTSTATUS smb2_setatr(struct smb2_tree *tree, const char *name, + uint32_t attrib) +{ + NTSTATUS status; + struct smb2_create create_io = {0}; + union smb_setfileinfo io; + + create_io.in.desired_access = SEC_FILE_READ_DATA | + SEC_FILE_WRITE_ATTRIBUTE; + create_io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = NTCREATEX_DISP_OPEN; + create_io.in.fname = name; + status = smb2_create(tree, tree, &create_io); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ZERO_STRUCT(io); + io.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION; + io.basic_info.in.file.handle = create_io.out.file.handle; + io.basic_info.in.attrib = attrib; + status = smb2_setinfo_file(tree, &io); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb2_util_close(tree, create_io.out.file.handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return status; +} + +bool torture_smb2_openattrtest(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + const char *fname = "openattr.file"; + uint16_t attr; + unsigned int i, j, k, l; + int ret = true; + + for (k = 0, i = 0; i < sizeof(open_attrs_table)/sizeof(uint32_t); i++) { + struct smb2_create create_io = {0}; + smb2_setatr(tree, fname, FILE_ATTRIBUTE_NORMAL); + smb2_util_unlink(tree, fname); + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_FILE_WRITE_DATA; + create_io.in.file_attributes = open_attrs_table[i]; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + create_io.in.create_options = 0; + create_io.in.security_flags = 0; + create_io.in.fname = fname; + status = smb2_create(tree, tctx, &create_io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "open %d (1) of %s failed (%s)", + i, fname, nt_errstr(status))); + + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "close %d (1) of %s failed (%s)", + i, fname, nt_errstr(status))); + + for (j = 0; j < ARRAY_SIZE(open_attrs_table); j++) { + create_io = (struct smb2_create){0}; + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_FILE_READ_DATA| + SEC_FILE_WRITE_DATA; + create_io.in.file_attributes = open_attrs_table[j]; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + create_io.in.create_options = 0; + create_io.in.security_flags = 0; + create_io.in.fname = fname; + status = smb2_create(tree, tctx, &create_io); + + if (!NT_STATUS_IS_OK(status)) { + for (l = 0; l < ARRAY_SIZE(attr_results); l++) { + torture_assert_goto(tctx, + attr_results[l].num != k, + ret, error_exit, + talloc_asprintf(tctx, + "[%d] trunc open 0x%x " + "-> 0x%x of %s failed " + "- should have " + "succeeded !(%s)", + k, open_attrs_table[i], + open_attrs_table[j], + fname, + nt_errstr(status))); + } + torture_assert_ntstatus_equal_goto(tctx, + status, NT_STATUS_ACCESS_DENIED, + ret, error_exit, + talloc_asprintf(tctx, + "[%d] trunc open 0x%x " + "-> 0x%x failed with " + "wrong error code %s", + k, open_attrs_table[i], + open_attrs_table[j], + nt_errstr(status))); + k++; + continue; + } + + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, + error_exit, talloc_asprintf(tctx, + "close %d (2) of %s failed (%s)", j, + fname, nt_errstr(status))); + + status = smb2_util_getatr(tree, fname, &attr, NULL, NULL); + torture_assert_ntstatus_ok_goto(tctx, status, ret, + error_exit, talloc_asprintf(tctx, + "getatr(2) failed (%s)", + nt_errstr(status))); + + for (l = 0; l < ARRAY_SIZE(attr_results); l++) { + if (attr_results[l].num == k) { + if (attr != attr_results[l].result_attr || + open_attrs_table[i] != attr_results[l].init_attr || + open_attrs_table[j] != attr_results[l].trunc_attr) { + ret = false; + torture_fail_goto(tctx, error_exit, + talloc_asprintf(tctx, + "[%d] getatr check " + "failed. [0x%x] trunc " + "[0x%x] got attr 0x%x," + " should be 0x%x", + k, open_attrs_table[i], + open_attrs_table[j], + (unsigned int)attr, + attr_results[l].result_attr)); + } + break; + } + } + k++; + } + } +error_exit: + smb2_setatr(tree, fname, FILE_ATTRIBUTE_NORMAL); + smb2_util_unlink(tree, fname); + + + return ret; +} + +bool torture_smb2_winattrtest(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "winattr1.file"; + const char *dname = "winattr1.dir"; + uint16_t attr; + uint16_t j; + uint32_t aceno; + bool ret = true; + union smb_fileinfo query, query_org; + NTSTATUS status; + struct security_descriptor *sd1 = NULL, *sd2 = NULL; + struct smb2_create create_io = {0}; + ZERO_STRUCT(query); + ZERO_STRUCT(query_org); + + /* Test winattrs for file */ + smb2_util_unlink(tree, fname); + + /* Open a file*/ + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA | + SEC_STD_READ_CONTROL; + create_io.in.file_attributes = 0; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = FILE_SUPERSEDE; + create_io.in.create_options = 0; + create_io.in.security_flags = 0; + create_io.in.fname = fname; + status = smb2_create(tree, tctx, &create_io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "open(1) of %s failed (%s)\n", + fname, nt_errstr(status))); + + /* Get security descriptor and store it*/ + query_org.generic.level = RAW_FILEINFO_SEC_DESC; + query_org.generic.in.file.handle = create_io.out.file.handle; + query_org.query_secdesc.in.secinfo_flags = SECINFO_OWNER| + SECINFO_GROUP| + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &query_org); + if(!NT_STATUS_IS_OK(status)){ + NTSTATUS s = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, s, ret, error_exit, + talloc_asprintf(tctx, + "close(1) of %s failed (%s)\n", + fname, nt_errstr(s))); + ret = false; + torture_fail_goto(tctx, error_exit, talloc_asprintf(tctx, + "smb2_getinfo_file(1) of %s failed (%s)\n", + fname, nt_errstr(status))); + } + sd1 = query_org.query_secdesc.out.sd; + + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "close(1) of %s failed (%s)\n", + fname, nt_errstr(status))); + + /*Set and get attributes*/ + for (j = 0; j < ARRAY_SIZE(open_attrs_table); j++) { + status = smb2_setatr(tree, fname, open_attrs_table[j]); + torture_assert_ntstatus_ok_goto(tctx, status, ret, + error_exit, + talloc_asprintf(tctx, "setatr(2) failed (%s)", + nt_errstr(status))); + + status = smb2_util_getatr(tree, fname, &attr, NULL, NULL); + torture_assert_ntstatus_ok_goto(tctx, status, ret, + error_exit, + talloc_asprintf(tctx, "getatr(2) failed (%s)", + nt_errstr(status))); + + /* Check the result */ + torture_assert_goto(tctx, attr == open_attrs_table[j], ret, + error_exit, talloc_asprintf(tctx, + "getatr check failed. \ + Attr applied [0x%x],got attr 0x%x, \ + should be 0x%x ", open_attrs_table[j], + (uint16_t)attr, open_attrs_table[j])); + + create_io = (struct smb2_create){0}; + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_FILE_READ_ATTRIBUTE| + SEC_STD_READ_CONTROL; + create_io.in.file_attributes = 0; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = FILE_OPEN_IF; + create_io.in.create_options = 0; + create_io.in.security_flags = 0; + create_io.in.fname = fname; + status = smb2_create(tree, tctx, &create_io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, + error_exit, + talloc_asprintf(tctx, "open(2) of %s failed (%s)\n", + fname, nt_errstr(status))); + /*Get security descriptor */ + query.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + query.query_secdesc.in.file.handle = create_io.out.file.handle; + query.query_secdesc.in.secinfo_flags = SECINFO_OWNER| + SECINFO_GROUP| + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &query); + if(!NT_STATUS_IS_OK(status)){ + NTSTATUS s = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, s, ret, + error_exit, + talloc_asprintf(tctx, + "close(2) of %s failed (%s)\n", + fname, nt_errstr(s))); + ret = false; + torture_fail_goto(tctx, error_exit, + talloc_asprintf(tctx, + "smb2_getinfo_file(2) of %s failed (%s)\n", + fname, nt_errstr(status))); + } + sd2 = query.query_secdesc.out.sd; + + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "close(2) of %s failed (%s)\n", + fname, nt_errstr(status))); + + /*Compare security descriptors -- Must be same*/ + for (aceno=0;(sd1->dacl&&aceno < sd1->dacl->num_aces);aceno++){ + struct security_ace *ace1 = &sd1->dacl->aces[aceno]; + struct security_ace *ace2 = &sd2->dacl->aces[aceno]; + + torture_assert_goto(tctx, security_ace_equal(ace1, ace2), + ret, error_exit, + "ACLs changed! Not expected!\n"); + } + + torture_comment(tctx, "[%d] setattr = [0x%x] got attr 0x%x\n", + j, open_attrs_table[j], attr ); + + } + + +/* Check for Directory. */ + + smb2_deltree(tree, dname); + smb2_util_rmdir(tree, dname); + + /* Open a directory */ + create_io = (struct smb2_create){0}; + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_RIGHTS_DIR_ALL; + create_io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create_io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create_io.in.security_flags = 0; + create_io.in.fname = dname; + status = smb2_create(tree, tctx, &create_io); + + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, + "open (1) of %s failed (%s)", + dname, nt_errstr(status))); + + + /* Get Security Descriptor */ + query_org.generic.level = RAW_FILEINFO_SEC_DESC; + query_org.generic.in.file.handle = create_io.out.file.handle; + status = smb2_getinfo_file(tree, tctx, &query_org); + if(!NT_STATUS_IS_OK(status)){ + NTSTATUS s = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, s, ret, error_exit, + talloc_asprintf(tctx, + "close(1) of %s failed (%s)\n", + dname, nt_errstr(s))); + ret = false; + torture_fail_goto(tctx, error_exit, talloc_asprintf(tctx, + "smb2_getinfo_file(1) of %s failed (%s)\n", dname, + nt_errstr(status))); + } + sd1 = query_org.query_secdesc.out.sd; + + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, + "close (1) of %s failed (%s)", dname, + nt_errstr(status))); + + /* Set and get win attributes*/ + for (j = 1; j < ARRAY_SIZE(open_attrs_table); j++) { + + status = smb2_setatr(tree, dname, open_attrs_table[j]); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "setatr(2) failed (%s)", + nt_errstr(status))); + + status = smb2_util_getatr(tree, dname, &attr, NULL, NULL); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "getatr(2) failed (%s)", + nt_errstr(status))); + + torture_comment(tctx, "[%d] setatt = [0x%x] got attr 0x%x\n", + j, open_attrs_table[j], attr ); + + /* Check the result */ + torture_assert_goto(tctx, + attr == (open_attrs_table[j]|FILE_ATTRIBUTE_DIRECTORY), + ret, error_exit, talloc_asprintf(tctx, + "getatr check failed. set attr " + "[0x%x], got attr 0x%x, should be 0x%x\n", + open_attrs_table[j], (uint16_t)attr, + (unsigned int)(open_attrs_table[j]|FILE_ATTRIBUTE_DIRECTORY))); + + create_io = (struct smb2_create){0}; + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_RIGHTS_DIR_READ; + create_io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = NTCREATEX_DISP_OPEN; + create_io.in.create_options = 0; + create_io.in.security_flags = 0; + create_io.in.fname = dname; + status = smb2_create(tree, tctx, &create_io); + + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, + "open (2) of %s failed (%s)", + dname, nt_errstr(status))); + /* Get security descriptor */ + query.generic.level = RAW_FILEINFO_SEC_DESC; + query.generic.in.file.handle = create_io.out.file.handle; + status = smb2_getinfo_file(tree, tctx, &query); + if(!NT_STATUS_IS_OK(status)){ + NTSTATUS s = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, s, ret, error_exit, + talloc_asprintf(tctx, + "close (2) of %s failed (%s)", dname, + nt_errstr(s))); + ret = false; + torture_fail_goto(tctx, error_exit, + talloc_asprintf(tctx, + "smb2_getinfo_file(2) of %s failed(%s)\n", + dname, nt_errstr(status))); + } + sd2 = query.query_secdesc.out.sd; + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, + "close (2) of %s failed (%s)", dname, + nt_errstr(status))); + + /* Security descriptor must be same*/ + for (aceno=0;(sd1->dacl&&aceno < sd1->dacl->num_aces);aceno++){ + struct security_ace *ace1 = &sd1->dacl->aces[aceno]; + struct security_ace *ace2 = &sd2->dacl->aces[aceno]; + + torture_assert_goto(tctx, security_ace_equal(ace1, ace2), + ret, error_exit, + "ACLs changed! Not expected!\n"); + } + + } + +error_exit: + smb2_setatr(tree, fname, FILE_ATTRIBUTE_NORMAL); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, dname); + smb2_util_rmdir(tree, dname); + + return ret; +} + +bool torture_smb2_sdreadtest(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "sdread.file"; + bool ret = true; + union smb_fileinfo query; + NTSTATUS status; + struct security_descriptor *sd = NULL; + struct smb2_create create_io = {0}; + uint32_t sd_bits[] = { SECINFO_OWNER, + SECINFO_GROUP, + SECINFO_DACL }; + size_t i; + + ZERO_STRUCT(query); + + smb2_util_unlink(tree, fname); + + /* Create then close a file*/ + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + create_io.in.file_attributes = 0; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = FILE_SUPERSEDE; + create_io.in.create_options = 0; + create_io.in.security_flags = 0; + create_io.in.fname = fname; + status = smb2_create(tree, tctx, &create_io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "open(1) of %s failed (%s)\n", + fname, nt_errstr(status))); + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, error_exit, + talloc_asprintf(tctx, "close(1) of %s failed (%s)\n", + fname, nt_errstr(status))); + + /* + * Open the file with READ_ATTRIBUTES *only*, + * no READ_CONTROL. + * + * This should deny access for any attempt to + * get a security descriptor if we ask for + * any of OWNER|GROUP|DACL, but if + * we ask for *NO* info but still ask for + * the security descriptor, then Windows + * returns an ACL but with zero entries + * for OWNER|GROUP|DACL. + */ + + create_io = (struct smb2_create){0}; + create_io.in.create_flags = 0; + create_io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + create_io.in.file_attributes = 0; + create_io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create_io.in.create_disposition = FILE_OPEN; + create_io.in.create_options = 0; + create_io.in.security_flags = 0; + create_io.in.fname = fname; + status = smb2_create(tree, tctx, &create_io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, + error_exit, + talloc_asprintf(tctx, "open(2) of %s failed (%s)\n", + fname, nt_errstr(status))); + + /* Check asking for SD fails ACCESS_DENIED with actual bits set. */ + for (i = 0; i < ARRAY_SIZE(sd_bits); i++) { + query.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + query.query_secdesc.in.file.handle = create_io.out.file.handle; + query.query_secdesc.in.secinfo_flags = sd_bits[i]; + + status = smb2_getinfo_file(tree, tctx, &query); + + /* Must return ACESS_DENIED. */ + if(!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)){ + NTSTATUS s = smb2_util_close(tree, + create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, s, ret, + error_exit, + talloc_asprintf(tctx, + "close(2) of %s failed (%s)\n", + fname, nt_errstr(s))); + ret = false; + torture_fail_goto(tctx, error_exit, + talloc_asprintf(tctx, + "smb2_getinfo_file(2) of %s failed (%s)\n", + fname, nt_errstr(status))); + } + } + + /* + * Get security descriptor whilst asking for *NO* bits. + * This succeeds even though we don't have READ_CONTROL + * access but returns an SD with zero data. + */ + query.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + query.query_secdesc.in.file.handle = create_io.out.file.handle; + query.query_secdesc.in.secinfo_flags = 0; + + status = smb2_getinfo_file(tree, tctx, &query); + if(!NT_STATUS_IS_OK(status)){ + NTSTATUS s = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, s, ret, error_exit, + talloc_asprintf(tctx, + "close(3) of %s failed (%s)\n", + fname, nt_errstr(s))); + ret = false; + torture_fail_goto(tctx, error_exit, talloc_asprintf(tctx, + "smb2_getinfo_file(3) of %s failed (%s)\n", + fname, nt_errstr(status))); + } + + sd = query.query_secdesc.out.sd; + + /* Check it's empty. */ + torture_assert_goto(tctx, + (sd->owner_sid == NULL), + ret, + error_exit, + "sd->owner_sid != NULL\n"); + + torture_assert_goto(tctx, + (sd->group_sid == NULL), + ret, + error_exit, + "sd->group_sid != NULL\n"); + + torture_assert_goto(tctx, + (sd->dacl == NULL), + ret, + error_exit, + "sd->dacl != NULL\n"); + + status = smb2_util_close(tree, create_io.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, + status, + ret, + error_exit, + talloc_asprintf(tctx, "close(4) of %s failed (%s)\n", + fname, + nt_errstr(status))); + +error_exit: + + smb2_setatr(tree, fname, FILE_ATTRIBUTE_NORMAL); + smb2_util_unlink(tree, fname); + + return ret; +} diff --git a/source4/torture/smb2/block.c b/source4/torture/smb2/block.c new file mode 100644 index 0000000..b9982b0 --- /dev/null +++ b/source4/torture/smb2/block.c @@ -0,0 +1,446 @@ +/* + * Unix SMB/CIFS implementation. + * + * block SMB2 transports using iptables + * + * Copyright (C) Guenther Deschner, 2017 + * + * 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/>. + */ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "system/network.h" +#include "lib/util/util_net.h" +#include "torture/smb2/block.h" +#include "libcli/smb/smbXcli_base.h" +#include "lib/util/tevent_ntstatus.h" +#include "oplock_break_handler.h" +#include "lease_break_handler.h" + +/* + * OUTPUT + * | + * -----> SMBTORTURE_OUTPUT + * | + * -----> SMBTORTURE_transportname1 + * -----> SMBTORTURE_transportname2 + */ + + +static bool run_cmd(const char *cmd) +{ + int ret; + + DEBUG(10, ("%s will call '%s'\n", __location__, cmd)); + + ret = system(cmd); + if (ret) { + DEBUG(1, ("%s failed to execute system call: %s: %d\n", + __location__, cmd, ret)); + return false; + } + + return true; +} + +static const char *iptables_command(struct torture_context *tctx) +{ + return torture_setting_string(tctx, "iptables_command", + "/usr/sbin/iptables"); +} + +char *escape_shell_string(const char *src); + +/* + * iptables v1.6.1: chain name `SMBTORTURE_INPUT_tree1->session->transport' + * too long (must be under 29 chars) + * + * maybe truncate chainname ? + */ +static const char *samba_chain_name(struct torture_context *tctx, + const char *name, + const char *prefix) +{ + const char *s; + char *sm; + + s = talloc_asprintf(tctx, "%s_%s", prefix, name); + if (s == NULL) { + return NULL; + } + + sm = escape_shell_string(s); + if (sm == NULL) { + return NULL; + } + + s = talloc_strdup(tctx, sm); + free(sm); + + return s; +} + +static bool iptables_setup_chain(struct torture_context *tctx, + const char *parent_chain, + const char *chain, + bool unblock) +{ + const char *ipt = iptables_command(tctx); + const char *cmd; + + if (unblock) { + cmd = talloc_asprintf(tctx, + "%s -L %s > /dev/null 2>&1 && " + "(" + "%s -F %s;" + "%s -D %s -j %s > /dev/null 2>&1 || true;" + "%s -X %s;" + ");" + "%s -L %s > /dev/null 2>&1 || true;", + ipt, chain, + ipt, chain, + ipt, parent_chain, chain, + ipt, chain, + ipt, chain); + } else { + cmd = talloc_asprintf(tctx, + "%s -L %s > /dev/null 2>&1 || " + "(" + "%s -N %s && " + "%s -I %s -j %s;" + ");" + "%s -F %s;", + ipt, chain, + ipt, chain, + ipt, parent_chain, chain, + ipt, chain); + } + + if (cmd == NULL) { + return false; + } + + if (!run_cmd(cmd)) { + return false; + } + + return true; +} + +uint16_t torture_get_local_port_from_transport(struct smb2_transport *t) +{ + const struct sockaddr_storage *local_ss; + + local_ss = smbXcli_conn_local_sockaddr(t->conn); + + return get_sockaddr_port(local_ss); +} + +static bool torture_block_tcp_output_port_internal( + struct torture_context *tctx, + const char *name, + uint16_t port, + bool unblock) +{ + const char *ipt = iptables_command(tctx); + const char *chain_out = NULL; + char *cmd_out = NULL; + + chain_out = samba_chain_name(tctx, name, "SMBTORTURE"); + if (chain_out == NULL) { + return false; + } + + torture_comment(tctx, "%sblocking %s dport %d\n", + unblock ? "un" : "", name, port); + + if (!unblock) { + bool ok; + + iptables_setup_chain(tctx, + "SMBTORTURE_OUTPUT", + chain_out, + true); + ok = iptables_setup_chain(tctx, + "SMBTORTURE_OUTPUT", + chain_out, + false); + if (!ok) { + return false; + } + } + + cmd_out = talloc_asprintf(tctx, + "%s %s %s -p tcp --sport %d -j DROP", + ipt, unblock ? "-D" : "-I", chain_out, port); + if (cmd_out == NULL) { + return false; + } + + if (!run_cmd(cmd_out)) { + return false; + } + + if (unblock) { + bool ok; + + ok = iptables_setup_chain(tctx, + "SMBTORTURE_OUTPUT", + chain_out, + true); + if (!ok) { + return false; + } + } + + return true; +} + +bool torture_block_tcp_output_port(struct torture_context *tctx, + const char *name, + uint16_t port) +{ + return torture_block_tcp_output_port_internal(tctx, name, port, false); +} + +bool torture_unblock_tcp_output_port(struct torture_context *tctx, + const char *name, + uint16_t port) +{ + return torture_block_tcp_output_port_internal(tctx, name, port, true); +} + +bool torture_block_tcp_output_setup(struct torture_context *tctx) +{ + return iptables_setup_chain(tctx, "OUTPUT", "SMBTORTURE_OUTPUT", false); +} + +bool torture_unblock_tcp_output_cleanup(struct torture_context *tctx) +{ + return iptables_setup_chain(tctx, "OUTPUT", "SMBTORTURE_OUTPUT", true); +} + +/* + * Use iptables to block channels + */ +static bool test_block_smb2_transport_iptables(struct torture_context *tctx, + struct smb2_transport *transport, + const char *name) +{ + uint16_t local_port; + bool ret; + + local_port = torture_get_local_port_from_transport(transport); + torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port); + ret = torture_block_tcp_output_port(tctx, name, local_port); + torture_assert(tctx, ret, "we could not block tcp transport"); + + return ret; +} + +static bool test_unblock_smb2_transport_iptables(struct torture_context *tctx, + struct smb2_transport *transport, + const char *name) +{ + uint16_t local_port; + bool ret; + + local_port = torture_get_local_port_from_transport(transport); + torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port); + ret = torture_unblock_tcp_output_port(tctx, name, local_port); + torture_assert(tctx, ret, "we could not block tcp transport"); + + return ret; +} + +static bool torture_blocked_lease_handler(struct smb2_transport *transport, + const struct smb2_lease_break *lb, + void *private_data) +{ + struct smb2_transport *transport_copy = + talloc_get_type_abort(private_data, + struct smb2_transport); + bool lease_skip_ack = lease_break_info.lease_skip_ack; + bool ok; + + lease_break_info.lease_skip_ack = true; + ok = transport_copy->lease.handler(transport, + lb, + transport_copy->lease.private_data); + lease_break_info.lease_skip_ack = lease_skip_ack; + + if (!ok) { + return false; + } + + if (lease_break_info.lease_skip_ack) { + return true; + } + + if (lb->break_flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) { + lease_break_info.failures++; + } + + return true; +} + +static bool torture_blocked_oplock_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_transport *transport_copy = + talloc_get_type_abort(private_data, + struct smb2_transport); + bool oplock_skip_ack = break_info.oplock_skip_ack; + bool ok; + + break_info.oplock_skip_ack = true; + ok = transport_copy->oplock.handler(transport, + handle, + level, + transport_copy->oplock.private_data); + break_info.oplock_skip_ack = oplock_skip_ack; + + if (!ok) { + return false; + } + + if (break_info.oplock_skip_ack) { + return true; + } + + break_info.failures++; + break_info.failure_status = NT_STATUS_CONNECTION_DISCONNECTED; + + return true; +} + +static bool test_block_smb2_transport_fsctl_smbtorture(struct torture_context *tctx, + struct smb2_transport *transport, + const char *name) +{ + struct smb2_transport *transport_copy = NULL; + DATA_BLOB in_input_buffer = data_blob_null; + DATA_BLOB in_output_buffer = data_blob_null; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + struct tevent_req *req = NULL; + uint16_t local_port; + NTSTATUS status; + bool ok; + + transport_copy = talloc_zero(transport, struct smb2_transport); + torture_assert(tctx, transport_copy, "talloc transport_copy"); + transport_copy->lease = transport->lease; + transport_copy->oplock = transport->oplock; + + local_port = torture_get_local_port_from_transport(transport); + torture_comment(tctx, "transport[%s] uses tcp port: %d\n", name, local_port); + req = smb2cli_ioctl_send(tctx, + tctx->ev, + transport->conn, + 1000, /* timeout_msec */ + NULL, /* session */ + NULL, /* tcon */ + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT, + 0, /* in_max_input_length */ + &in_input_buffer, + 0, /* in_max_output_length */ + &in_output_buffer, + SMB2_IOCTL_FLAG_IS_FSCTL); + torture_assert(tctx, req != NULL, "smb2cli_ioctl_send() failed"); + ok = tevent_req_poll_ntstatus(req, tctx->ev, &status); + if (ok) { + status = NT_STATUS_OK; + } + torture_assert_ntstatus_ok(tctx, status, "tevent_req_poll_ntstatus() failed"); + status = smb2cli_ioctl_recv(req, tctx, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_ok(tctx, status, + "FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT failed\n\n" + "On a Samba server 'smbd:FSCTL_SMBTORTURE = yes' is needed!\n\n" + "Otherwise you may need to use iptables like this:\n" + "--option='torture:use_iptables=yes'\n" + "And maybe something like this in addition:\n" + "--option='torture:iptables_command=sudo /sbin/iptables'\n\n"); + TALLOC_FREE(req); + + if (transport->lease.handler != NULL) { + transport->lease.handler = torture_blocked_lease_handler; + transport->lease.private_data = transport_copy; + } + if (transport->oplock.handler != NULL) { + transport->oplock.handler = torture_blocked_oplock_handler; + transport->oplock.private_data = transport_copy; + } + + return true; +} + +bool _test_block_smb2_transport(struct torture_context *tctx, + struct smb2_transport *transport, + const char *name) +{ + bool use_iptables = torture_setting_bool(tctx, + "use_iptables", false); + + if (use_iptables) { + return test_block_smb2_transport_iptables(tctx, transport, name); + } else { + return test_block_smb2_transport_fsctl_smbtorture(tctx, transport, name); + } +} + +bool _test_unblock_smb2_transport(struct torture_context *tctx, + struct smb2_transport *transport, + const char *name) +{ + bool use_iptables = torture_setting_bool(tctx, + "use_iptables", false); + + if (use_iptables) { + return test_unblock_smb2_transport_iptables(tctx, transport, name); + } else { + return true; + } +} + +bool test_setup_blocked_transports(struct torture_context *tctx) +{ + bool use_iptables = torture_setting_bool(tctx, + "use_iptables", false); + + if (use_iptables) { + return torture_block_tcp_output_setup(tctx); + } + + return true; +} + +void test_cleanup_blocked_transports(struct torture_context *tctx) +{ + bool use_iptables = torture_setting_bool(tctx, + "use_iptables", false); + + if (use_iptables) { + torture_unblock_tcp_output_cleanup(tctx); + } +} diff --git a/source4/torture/smb2/block.h b/source4/torture/smb2/block.h new file mode 100644 index 0000000..6a6370a --- /dev/null +++ b/source4/torture/smb2/block.h @@ -0,0 +1,43 @@ +/* + * Unix SMB/CIFS implementation. + * + * block SMB2 transports using iptables + * + * Copyright (C) Guenther Deschner, 2017 + * + * 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/>. + */ + +uint16_t torture_get_local_port_from_transport(struct smb2_transport *t); + +bool torture_block_tcp_output_port(struct torture_context *tctx, + const char *name, + uint16_t port); +bool torture_unblock_tcp_output_port(struct torture_context *tctx, + const char *name, + uint16_t port); +bool torture_block_tcp_output_setup(struct torture_context *tctx); +bool torture_unblock_tcp_output_cleanup(struct torture_context *tctx); + +bool test_setup_blocked_transports(struct torture_context *tctx); +void test_cleanup_blocked_transports(struct torture_context *tctx); + +#define test_block_smb2_transport(_tctx, _t) _test_block_smb2_transport(_tctx, _t, #_t) +bool _test_block_smb2_transport(struct torture_context *tctx, + struct smb2_transport *transport, + const char *name); +#define test_unblock_smb2_transport(_tctx, _t) _test_unblock_smb2_transport(_tctx, _t, #_t) +bool _test_unblock_smb2_transport(struct torture_context *tctx, + struct smb2_transport *transport, + const char *name); diff --git a/source4/torture/smb2/charset.c b/source4/torture/smb2/charset.c new file mode 100644 index 0000000..a385266 --- /dev/null +++ b/source4/torture/smb2/charset.c @@ -0,0 +1,235 @@ +/* + Unix SMB/CIFS implementation. + + SMB torture tester - charset test routines + + Copyright (C) Andrew Tridgell 2001 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "param/param.h" + +#define BASEDIR "chartest" + +/* + open a file using a set of unicode code points for the name + + the prefix BASEDIR is added before the name +*/ +static NTSTATUS unicode_open(struct torture_context *tctx, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + uint32_t create_disposition, + const uint32_t *u_name, + size_t u_name_len) +{ + struct smb2_create io = {0}; + char *fname = NULL; + char *fname2 = NULL; + char *ucs_name = NULL; + size_t i; + NTSTATUS status; + + ucs_name = talloc_size(mem_ctx, (1+u_name_len)*2); + if (!ucs_name) { + torture_comment(tctx, "Failed to create UCS2 Name - talloc() failure\n"); + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<u_name_len;i++) { + SSVAL(ucs_name, i*2, u_name[i]); + } + SSVAL(ucs_name, i*2, 0); + + if (!convert_string_talloc_handle(ucs_name, lpcfg_iconv_handle(tctx->lp_ctx), CH_UTF16, CH_UNIX, ucs_name, (1+u_name_len)*2, (void **)&fname, &i)) { + torture_comment(tctx, "Failed to convert UCS2 Name into unix - convert_string_talloc() failure\n"); + talloc_free(ucs_name); + return NT_STATUS_NO_MEMORY; + } + + fname2 = talloc_asprintf(ucs_name, "%s\\%s", BASEDIR, fname); + if (!fname2) { + talloc_free(ucs_name); + torture_comment(tctx, "Failed to create fname - talloc() failure\n"); + return NT_STATUS_NO_MEMORY; + } + + io.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.in.create_options = 0; + io.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname2; + io.in.create_disposition = create_disposition; + + status = smb2_create(tree, tctx, &io); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(ucs_name); + return status; + } + + smb2_util_close(tree, io.out.file.handle); + talloc_free(ucs_name); + return NT_STATUS_OK; +} + + +/* + see if the server recognises composed characters +*/ +static bool test_composed(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const uint32_t name1[] = {0x61, 0x308}; + const uint32_t name2[] = {0xe4}; + NTSTATUS status; + bool ret = true; + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, "setting up basedir"); + + status = unicode_open(tctx, tree, tctx, + NTCREATEX_DISP_CREATE, name1, 2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create composed name"); + + status = unicode_open(tctx, tree, tctx, + NTCREATEX_DISP_CREATE, name2, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create accented character"); + +done: + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + see if the server recognises a naked diacritical +*/ +static bool test_diacritical(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const uint32_t name1[] = {0x308}; + const uint32_t name2[] = {0x308, 0x308}; + NTSTATUS status; + bool ret = true; + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, "setting up basedir"); + + status = unicode_open(tctx, tree, tctx, + NTCREATEX_DISP_CREATE, name1, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create naked diacritical"); + + /* try a double diacritical */ + status = unicode_open(tctx, tree, tctx, + NTCREATEX_DISP_CREATE, name2, 2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create double " + "naked diacritical"); + +done: + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + see if the server recognises a partial surrogate pair +*/ +static bool test_surrogate(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const uint32_t name1[] = {0xd800}; + const uint32_t name2[] = {0xdc00}; + const uint32_t name3[] = {0xd800, 0xdc00}; + NTSTATUS status; + bool ret = true; + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, "setting up basedir"); + + status = unicode_open(tctx, tree, tctx, NTCREATEX_DISP_CREATE, name1, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create partial surrogate 1"); + + status = unicode_open(tctx, tree, tctx, NTCREATEX_DISP_CREATE, name2, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create partial surrogate 2"); + + status = unicode_open(tctx, tree, tctx, NTCREATEX_DISP_CREATE, name3, 2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create full surrogate"); + +done: + smb2_deltree(tree, BASEDIR); + return true; +} + +/* + see if the server recognises wide-a characters +*/ +static bool test_widea(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const uint32_t name1[] = {'a'}; + const uint32_t name2[] = {0xff41}; + const uint32_t name3[] = {0xff21}; + NTSTATUS status; + bool ret = true; + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert_goto(tctx, ret, ret, done, "setting up basedir"); + + status = unicode_open(tctx, tree, tctx, NTCREATEX_DISP_CREATE, name1, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create 'a'"); + + status = unicode_open(tctx, tree, tctx, NTCREATEX_DISP_CREATE, name2, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to create wide-a"); + + status = unicode_open(tctx, tree, tctx, NTCREATEX_DISP_CREATE, name3, 1); + torture_assert_ntstatus_equal_goto(tctx, + status, + NT_STATUS_OBJECT_NAME_COLLISION, + ret, done, + "Failed to create wide-A"); + +done: + smb2_deltree(tree, BASEDIR); + return ret; +} + +struct torture_suite *torture_smb2_charset(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "charset"); + + torture_suite_add_1smb2_test(suite, "Testing composite character (a umlaut)", test_composed); + torture_suite_add_1smb2_test(suite, "Testing naked diacritical (umlaut)", test_diacritical); + torture_suite_add_1smb2_test(suite, "Testing partial surrogate", test_surrogate); + torture_suite_add_1smb2_test(suite, "Testing wide-a", test_widea); + + return suite; +} diff --git a/source4/torture/smb2/compound.c b/source4/torture/smb2/compound.c new file mode 100644 index 0000000..47a550f --- /dev/null +++ b/source4/torture/smb2/compound.c @@ -0,0 +1,2341 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 compounded requests + + Copyright (C) Stefan Metzmacher 2009 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "../libcli/smb/smbXcli_base.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ + nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect value %s=%d - should be %d\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +static struct { + struct smb2_handle handle; + uint8_t level; + struct smb2_break br; + int count; + int failures; + NTSTATUS failure_status; +} break_info; + +static void torture_oplock_break_callback(struct smb2_request *req) +{ + NTSTATUS status; + struct smb2_break br; + + ZERO_STRUCT(br); + status = smb2_break_recv(req, &break_info.br); + if (!NT_STATUS_IS_OK(status)) { + break_info.failures++; + break_info.failure_status = status; + } + + return; +} + +/* A general oplock break notification handler. This should be used when a + * test expects to break from batch or exclusive to a lower level. */ +static bool torture_oplock_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_tree *tree = private_data; + const char *name; + struct smb2_request *req; + ZERO_STRUCT(break_info.br); + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + switch (level) { + case SMB2_OPLOCK_LEVEL_II: + name = "level II"; + break; + case SMB2_OPLOCK_LEVEL_NONE: + name = "none"; + break; + default: + name = "unknown"; + break_info.failures++; + } + printf("Acking to %s [0x%02X] in oplock handler\n", name, level); + + break_info.br.in.file.handle = *handle; + break_info.br.in.oplock_level = level; + break_info.br.in.reserved = 0; + break_info.br.in.reserved2 = 0; + + req = smb2_break_send(tree, &break_info.br); + req->async.fn = torture_oplock_break_callback; + req->async.private_data = NULL; + return true; +} + +static bool test_compound_break(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname1 = "some-file.pptx"; + NTSTATUS status; + bool ret = true; + union smb_open io1; + struct smb2_create io2; + struct smb2_getinfo gf; + struct smb2_request *req[2]; + struct smb2_handle h1; + struct smb2_handle h; + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + ZERO_STRUCT(break_info); + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io1.smb2); + io1.generic.level = RAW_OPEN_SMB2; + io1.smb2.in.desired_access = (SEC_STD_SYNCHRONIZE| + SEC_STD_READ_CONTROL| + SEC_FILE_READ_ATTRIBUTE| + SEC_FILE_READ_EA| + SEC_FILE_READ_DATA); + io1.smb2.in.alloc_size = 0; + io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io1.smb2.in.create_options = 0; + io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io1.smb2.in.security_flags = 0; + io1.smb2.in.fname = fname1; + + torture_comment(tctx, "TEST2: open a file with an batch " + "oplock (share mode: all)\n"); + io1.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree, tctx, &(io1.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + + h1 = io1.smb2.out.file.handle; + + torture_comment(tctx, "TEST2: Opening second time with compound\n"); + + ZERO_STRUCT(io2); + + io2.in.desired_access = (SEC_STD_SYNCHRONIZE| + SEC_FILE_READ_ATTRIBUTE| + SEC_FILE_READ_EA); + io2.in.alloc_size = 0; + io2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io2.in.create_disposition = NTCREATEX_DISP_OPEN; + io2.in.create_options = 0; + io2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io2.in.security_flags = 0; + io2.in.fname = fname1; + io2.in.oplock_level = 0; + + smb2_transport_compound_start(tree->session->transport, 2); + + req[0] = smb2_create_send(tree, &io2); + + smb2_transport_compound_set_related(tree->session->transport, true); + + h.data[0] = UINT64_MAX; + h.data[1] = UINT64_MAX; + + ZERO_STRUCT(gf); + gf.in.file.handle = h; + gf.in.info_type = SMB2_0_INFO_FILE; + gf.in.info_class = 0x16; + gf.in.output_buffer_length = 0x1000; + gf.in.input_buffer = data_blob_null; + + req[1] = smb2_getinfo_send(tree, &gf); + + status = smb2_create_recv(req[0], tree, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_getinfo_recv(req[1], tree, &gf); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname1); + return ret; +} + +static bool test_compound_related1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_related1.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[2]; + struct smbXcli_tcon *saved_tcon = tree->smbXcli; + struct smbXcli_session *saved_session = tree->session->smbXcli; + + smb2_transport_credits_ask_num(tree->session->transport, 2); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 2); + + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + tree->smbXcli = smbXcli_tcon_create(tree); + smb2cli_tcon_set_values(tree->smbXcli, + NULL, /* session */ + 0xFFFFFFFF, /* tcon_id */ + 0, /* type */ + 0, /* flags */ + 0, /* capabilities */ + 0 /* maximal_access */); + + tree->session->smbXcli = smbXcli_session_shallow_copy(tree->session, + tree->session->smbXcli); + smb2cli_session_set_id_and_flags(tree->session->smbXcli, UINT64_MAX, 0); + + req[1] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + TALLOC_FREE(tree->smbXcli); + tree->smbXcli = saved_tcon; + TALLOC_FREE(tree->session->smbXcli); + tree->session->smbXcli = saved_session; + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_related2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_related2.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + struct smbXcli_tcon *saved_tcon = tree->smbXcli; + struct smbXcli_session *saved_session = tree->session->smbXcli; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + tree->smbXcli = smbXcli_tcon_create(tree); + smb2cli_tcon_set_values(tree->smbXcli, + NULL, /* session */ + 0xFFFFFFFF, /* tcon_id */ + 0, /* type */ + 0, /* flags */ + 0, /* capabilities */ + 0 /* maximal_access */); + + tree->session->smbXcli = smbXcli_session_shallow_copy(tree->session, + tree->session->smbXcli); + smb2cli_session_set_id_and_flags(tree->session->smbXcli, UINT64_MAX, 0); + + req[1] = smb2_close_send(tree, &cl); + req[2] = smb2_close_send(tree, &cl); + req[3] = smb2_close_send(tree, &cl); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + TALLOC_FREE(tree->smbXcli); + tree->smbXcli = saved_tcon; + TALLOC_FREE(tree->session->smbXcli); + tree->session->smbXcli = saved_session; + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_related3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_ioctl io; + struct smb2_create cr; + struct smb2_close cl; + const char *fname = "compound_related3.dat"; + struct smb2_request *req[3]; + NTSTATUS status; + bool ret = false; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 3); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(io); + io.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID; + io.in.file.handle = hd; + io.in.reserved2 = 0; + io.in.max_output_response = 64; + io.in.flags = 1; + + req[1] = smb2_ioctl_send(tree, &io); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_ioctl_recv(req[1], tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + ret = true; +done: + return ret; +} + +static bool test_compound_related4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related4.dat"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_ioctl io; + struct smb2_close cl; + struct smb2_request *req[4]; + NTSTATUS status; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + hd = cr.out.file.handle; + torture_comment(tctx, "set a sec desc allowing no write by CREATOR_OWNER\n"); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + torture_comment(tctx, "try open for write\n"); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + smb2_transport_compound_start(tree->session->transport, 4); + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + ZERO_STRUCT(io); + io.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID; + io.in.file.handle = hd; + io.in.flags = 1; + + req[1] = smb2_ioctl_send(tree, &io); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_ioctl_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_create_send failed\n"); + + set.set_secdesc.in.file.handle = hd; + + req[3] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_create_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_create_recv failed\n"); + + status = smb2_ioctl_recv(req[1], tree, &io); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_ioctl_recv failed\n"); + + status = smb2_close_recv(req[2], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[3]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related5(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_ioctl io; + struct smb2_close cl; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = false; + + smb2_transport_compound_start(tree->session->transport, 2); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(io); + io.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID; + io.in.file.handle = hd; + io.in.flags = 1; + + req[0] = smb2_ioctl_send(tree, &io); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_ioctl_send failed\n"); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[1] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_create_send failed\n"); + + status = smb2_ioctl_recv(req[0], tree, &io); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_FILE_CLOSED, + ret, done, + "smb2_ioctl_recv failed\n"); + + status = smb2_close_recv(req[1], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_FILE_CLOSED, + ret, done, + "smb2_close_recv failed\n"); + + ret = true; + +done: + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related6(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + struct smb2_read rd; + struct smb2_write wr; + struct smb2_close cl; + NTSTATUS status; + const char *fname = "compound_related6.dat"; + struct smb2_request *req[5]; + uint8_t buf[64]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, hd, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + torture_comment(tctx, "try open for read\n"); + cr.in.desired_access = SEC_FILE_READ_DATA; + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(rd); + rd.in.file.handle = hd; + rd.in.length = 1; + rd.in.offset = 0; + + req[1] = smb2_read_send(tree, &rd); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_read_send failed\n"); + + ZERO_STRUCT(wr); + wr.in.file.handle = hd; + wr.in.offset = 0; + wr.in.data = data_blob_talloc(tctx, NULL, 64); + + req[2] = smb2_write_send(tree, &wr); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_write_send failed\n"); + + ZERO_STRUCT(rd); + rd.in.file.handle = hd; + rd.in.length = 1; + rd.in.offset = 0; + + req[3] = smb2_read_send(tree, &rd); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_read_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[4] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[4], ret, done, + "smb2_close_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create_recv failed\n"); + + status = smb2_read_recv(req[1], tree, &rd); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_read_recv failed\n"); + + status = smb2_write_recv(req[2], &wr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_write_recv failed\n"); + + status = smb2_read_recv(req[3], tree, &rd); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_read_recv failed\n"); + + status = smb2_close_recv(req[4], &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close_recv failed\n"); + + done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related7(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related4.dat"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_notify nt; + struct smb2_close cl; + NTSTATUS status; + struct smb2_request *req[4]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + torture_comment(tctx, "set a sec desc allowing no write by CREATOR_OWNER\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + torture_comment(tctx, "try open for write\n"); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + smb2_transport_compound_start(tree->session->transport, 4); + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_notify_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_close_send failed\n"); + + set.set_secdesc.in.file.handle = hd; + + req[3] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_setinfo_file_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_create_recv failed\n"); + + status = smb2_notify_recv(req[1], tree, &nt); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_notify_recv failed\n"); + + status = smb2_close_recv(req[2], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[3]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related8(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related8.dat"; + const char *fname_nonexisting = "compound_related8.dat.void"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_notify nt; + struct smb2_close cl; + NTSTATUS status; + struct smb2_request *req[4]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + + smb2_transport_compound_start(tree->session->transport, 4); + + torture_comment(tctx, "try open for write\n"); + cr.in.fname = fname_nonexisting; + cr.in.create_disposition = NTCREATEX_DISP_OPEN; + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_notify_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_close_send failed\n"); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + req[3] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_setinfo_file_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_create_recv failed\n"); + + status = smb2_notify_recv(req[1], tree, &nt); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_notify_recv failed\n"); + + status = smb2_close_recv(req[2], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[3]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related9(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related9.dat"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_notify nt; + struct smb2_close cl; + NTSTATUS status; + struct smb2_request *req[3]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + + smb2_transport_compound_start(tree->session->transport, 3); + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + + req[0] = smb2_notify_send(tree, &nt); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_notify_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[1] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_close_send failed\n"); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + req[2] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_setinfo_file_send failed\n"); + + status = smb2_notify_recv(req[0], tree, &nt); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_INVALID_PARAMETER, + ret, done, + "smb2_notify_recv failed\n"); + + status = smb2_close_recv(req[1], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_INVALID_PARAMETER, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[2]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_INVALID_PARAMETER, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_padding(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle h; + struct smb2_create cr; + struct smb2_read r; + const char *fname = "compound_read.dat"; + const char *sname = "compound_read.dat:foo"; + struct smb2_request *req[3]; + NTSTATUS status; + bool ret = false; + + smb2_util_unlink(tree, fname); + + /* Write file */ + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.fname = fname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree, tctx, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + h = cr.out.file.handle; + + status = smb2_util_write(tree, h, "123", 0, 3); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h); + + /* Write stream */ + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.fname = sname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree, tctx, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + h = cr.out.file.handle; + + status = smb2_util_write(tree, h, "456", 0, 3); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h); + + /* Check compound read from basefile */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(cr); + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.desired_access = SEC_FILE_READ_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_OPEN; + cr.in.fname = fname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(r); + h.data[0] = UINT64_MAX; + h.data[1] = UINT64_MAX; + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[1] = smb2_read_send(tree, &r); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[1]) || + !smb2_request_is_ok(req[1])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 24: 16 byte read response header plus 3 + * requested bytes padded to an 8 byte boundary. + */ + CHECK_VALUE(req[1]->in.body_size, 24); + + status = smb2_read_recv(req[1], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, cr.out.file.handle); + + /* Check compound read from stream */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(cr); + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.desired_access = SEC_FILE_READ_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_OPEN; + cr.in.fname = sname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(r); + h.data[0] = UINT64_MAX; + h.data[1] = UINT64_MAX; + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[1] = smb2_read_send(tree, &r); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[1]) || + !smb2_request_is_ok(req[1])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 24: 16 byte read response header plus 3 + * requested bytes padded to an 8 byte boundary. + */ + CHECK_VALUE(req[1]->in.body_size, 24); + + status = smb2_read_recv(req[1], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + h = cr.out.file.handle; + + /* Check 2 compound (unrelateated) reads from existing stream handle */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[0] = smb2_read_send(tree, &r); + req[1] = smb2_read_send(tree, &r); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[0]) || + !smb2_request_is_ok(req[0])) { + torture_fail(tctx, "failed to receive read request"); + } + if (!smb2_request_receive(req[1]) || + !smb2_request_is_ok(req[1])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 24: 16 byte read response header plus 3 + * requested bytes padded to an 8 byte boundary. + */ + CHECK_VALUE(req[0]->in.body_size, 24); + CHECK_VALUE(req[1]->in.body_size, 24); + + status = smb2_read_recv(req[0], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_read_recv(req[1], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * now try a single read from the stream and verify there's no padding + */ + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[0] = smb2_read_send(tree, &r); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[0]) || + !smb2_request_is_ok(req[0])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 19: 16 byte read response header plus 3 + * requested bytes without padding. + */ + CHECK_VALUE(req[0]->in.body_size, 19); + + status = smb2_read_recv(req[0], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + ret = true; +done: + return ret; +} + +static bool test_compound_create_write_close(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle handle = { .data = { UINT64_MAX, UINT64_MAX } }; + struct smb2_create create; + struct smb2_write write; + struct smb2_close close; + const char *fname = "compound_create_write_close.dat"; + struct smb2_request *req[3]; + NTSTATUS status; + bool ret = false; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(create); + create.in.security_flags = 0x00; + create.in.oplock_level = 0; + create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + create.in.create_flags = 0x00000000; + create.in.reserved = 0x00000000; + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + create.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 3); + + req[0] = smb2_create_send(tree, &create); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(write); + write.in.file.handle = handle; + write.in.offset = 0; + write.in.data = data_blob_talloc(tctx, NULL, 1024); + + req[1] = smb2_write_send(tree, &write); + + ZERO_STRUCT(close); + close.in.file.handle = handle; + + req[2] = smb2_close_send(tree, &close); + + status = smb2_create_recv(req[0], tree, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE failed."); + + status = smb2_write_recv(req[1], &write); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "WRITE failed."); + + status = smb2_close_recv(req[2], &close); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE failed."); + + status = smb2_util_unlink(tree, fname); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "File deletion failed."); + + ret = true; +done: + return ret; +} + +static bool test_compound_unrelated1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_unrelated1.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + req[1] = smb2_close_send(tree, &cl); + req[2] = smb2_close_send(tree, &cl); + req[3] = smb2_close_send(tree, &cl); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_invalid1.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[3]; + + smb2_transport_credits_ask_num(tree->session->transport, 3); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 3); + + /* passing the first request with the related flag is invalid */ + smb2_transport_compound_set_related(tree->session->transport, true); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + req[1] = smb2_close_send(tree, &cl); + + smb2_transport_compound_set_related(tree->session->transport, false); + req[2] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + /* TODO: check why this fails with --signing=required */ + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_invalid2.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + struct smbXcli_tcon *saved_tcon = tree->smbXcli; + struct smbXcli_session *saved_session = tree->session->smbXcli; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + tree->smbXcli = smbXcli_tcon_create(tree); + smb2cli_tcon_set_values(tree->smbXcli, + NULL, /* session */ + 0xFFFFFFFF, /* tcon_id */ + 0, /* type */ + 0, /* flags */ + 0, /* capabilities */ + 0 /* maximal_access */); + + tree->session->smbXcli = smbXcli_session_shallow_copy(tree->session, + tree->session->smbXcli); + smb2cli_session_set_id_and_flags(tree->session->smbXcli, UINT64_MAX, 0); + + req[1] = smb2_close_send(tree, &cl); + /* strange that this is not generating invalid parameter */ + smb2_transport_compound_set_related(tree->session->transport, false); + req[2] = smb2_close_send(tree, &cl); + req[3] = smb2_close_send(tree, &cl); + smb2_transport_compound_set_related(tree->session->transport, true); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + TALLOC_FREE(tree->smbXcli); + tree->smbXcli = saved_tcon; + TALLOC_FREE(tree->session->smbXcli); + tree->session->smbXcli = saved_session; + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_invalid3.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + req[1] = smb2_close_send(tree, &cl); + req[2] = smb2_close_send(tree, &cl); + /* flipping the related flag is invalid */ + smb2_transport_compound_set_related(tree->session->transport, true); + req[3] = smb2_close_send(tree, &cl); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_read rd; + NTSTATUS status; + const char *fname = "compound_invalid4.dat"; + struct smb2_close cl; + bool ret = true; + bool ok; + struct smb2_request *req[2]; + + smb2_transport_credits_ask_num(tree->session->transport, 2); + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(rd); + rd.in.file.handle = cr.out.file.handle; + rd.in.length = 1; + rd.in.offset = 0; + req[0] = smb2_read_send(tree, &rd); + + smb2_transport_compound_set_related(tree->session->transport, true); + + /* + * Send a completely bogus request as second compound + * element. This triggers smbd_smb2_request_error() in in + * smbd_smb2_request_dispatch() before calling + * smbd_smb2_request_dispatch_update_counts(). + */ + + req[1] = smb2_request_init_tree(tree, 0xff, 0x04, false, 0); + smb2_transport_send(req[1]); + + status = smb2_read_recv(req[0], tctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + ok = smb2_request_receive(req[1]); + torture_assert(tctx, ok, "Invalid request failed\n"); + CHECK_STATUS(req[1]->status, NT_STATUS_INVALID_PARAMETER); + + ZERO_STRUCT(cl); + cl.in.file.handle = cr.out.file.handle; + + status = smb2_close(tree, &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +/* Send a compound request where we expect the last request (Create, Notify) + * to go asynchronous. This works against a Win7 server and the reply is + * sent in two different packets. */ +static bool test_compound_interim1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status = NT_STATUS_OK; + const char *dname = "compound_interim_dir"; + struct smb2_notify nt; + bool ret = true; + struct smb2_request *req[2]; + + /* Win7 compound request implementation deviates substantially from the + * SMB2 spec as noted in MS-SMB2 <159>, <162>. This, test currently + * verifies the Windows behavior, not the general spec behavior. */ + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_deltree(tree, dname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + cr.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.fname = dname; + + smb2_transport_compound_start(tree->session->transport, 2); + + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_cancel(req[1]); + status = smb2_notify_recv(req[1], tree, &nt); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + smb2_util_close(tree, cr.out.file.handle); + + smb2_deltree(tree, dname); +done: + return ret; +} + +/* Send a compound request where we expect the middle request (Create, Notify, + * GetInfo) to go asynchronous. Against Win7 the sync request succeed while + * the async fails. All are returned in the same compound response. */ +static bool test_compound_interim2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status = NT_STATUS_OK; + const char *dname = "compound_interim_dir"; + struct smb2_getinfo gf; + struct smb2_notify nt; + bool ret = true; + struct smb2_request *req[3]; + + /* Win7 compound request implementation deviates substantially from the + * SMB2 spec as noted in MS-SMB2 <159>, <162>. This, test currently + * verifies the Windows behavior, not the general spec behavior. */ + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_deltree(tree, dname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + cr.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.fname = dname; + + smb2_transport_compound_start(tree->session->transport, 3); + + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + + ZERO_STRUCT(gf); + gf.in.file.handle = hd; + gf.in.info_type = SMB2_0_INFO_FILE; + gf.in.info_class = 0x04; /* FILE_BASIC_INFORMATION */ + gf.in.output_buffer_length = 0x1000; + gf.in.input_buffer = data_blob_null; + + req[2] = smb2_getinfo_send(tree, &gf); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req[1], tree, &nt); + CHECK_STATUS(status, NT_STATUS_INTERNAL_ERROR); + + status = smb2_getinfo_recv(req[2], tree, &gf); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, cr.out.file.handle); + + smb2_deltree(tree, dname); +done: + return ret; +} + +/* Test compound related finds */ +static bool test_compound_find_related(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *dname = "compound_find_dir"; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, dname); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = dname; + + status = smb2_create(tree, mem_ctx, &create); + h = create.out.file.handle; + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + req[0] = smb2_find_send(tree, &f); + + smb2_transport_compound_set_related(tree->session->transport, true); + + req[1] = smb2_find_send(tree, &f); + + status = smb2_find_recv(req[0], mem_ctx, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_recv failed\n"); + + status = smb2_find_recv(req[1], mem_ctx, &f); + torture_assert_ntstatus_equal_goto(tctx, status, STATUS_NO_MORE_FILES, ret, done, "smb2_find_recv failed\n"); + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, dname); + TALLOC_FREE(mem_ctx); + return ret; +} + +/* Test compound related finds */ +static bool test_compound_find_close(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *dname = "compound_find_dir"; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h; + struct smb2_request *req = NULL; + const int num_files = 5000; + int i; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, dname); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = dname; + + smb2cli_conn_set_max_credits(tree->session->transport->conn, 256); + + status = smb2_create(tree, mem_ctx, &create); + h = create.out.file.handle; + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + for (i = 0; i < num_files; i++) { + create.in.fname = talloc_asprintf(mem_ctx, "%s\\file%d", + dname, i); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + smb2_util_close(tree, create.out.file.handle); + } + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.max_response_size = 8*1024*1024; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + req = smb2_find_send(tree, &f); + + status = smb2_util_close(tree, h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed\n"); + + status = smb2_find_recv(req, mem_ctx, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_recv failed\n"); + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, dname); + TALLOC_FREE(mem_ctx); + return ret; +} + +/* Test compound unrelated finds */ +static bool test_compound_find_unrelated(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *dname = "compound_find_dir"; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, dname); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = dname; + + status = smb2_create(tree, mem_ctx, &create); + h = create.out.file.handle; + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + req[0] = smb2_find_send(tree, &f); + req[1] = smb2_find_send(tree, &f); + + status = smb2_find_recv(req[0], mem_ctx, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_recv failed\n"); + + status = smb2_find_recv(req[1], mem_ctx, &f); + torture_assert_ntstatus_equal_goto(tctx, status, STATUS_NO_MORE_FILES, ret, done, "smb2_find_recv failed\n"); + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, dname); + TALLOC_FREE(mem_ctx); + return ret; +} + +static bool test_compound_async_flush_close(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle fhandle = { .data = { 0, 0 } }; + struct smb2_handle relhandle = { .data = { UINT64_MAX, UINT64_MAX } }; + struct smb2_close cl; + struct smb2_flush fl; + const char *fname = "compound_async_flush_close"; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = false; + + /* Start clean. */ + smb2_util_unlink(tree, fname); + + /* Create a file. */ + status = torture_smb2_testfile_access(tree, + fname, + &fhandle, + SEC_RIGHTS_FILE_ALL); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Now do a compound flush + close handle. */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(fl); + fl.in.file.handle = fhandle; + + req[0] = smb2_flush_send(tree, &fl); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_flush_send failed\n"); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = relhandle; + req[1] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_close_send failed\n"); + + status = smb2_flush_recv(req[0], &fl); + /* + * On Windows, this flush will usually + * succeed as we have nothing to flush, + * so allow NT_STATUS_OK. Once bug #15172 + * is fixed Samba will do the flush synchronously + * so allow NT_STATUS_OK. + */ + if (!NT_STATUS_IS_OK(status)) { + /* + * If we didn't get NT_STATUS_OK, we *must* + * get NT_STATUS_INTERNAL_ERROR if the flush + * goes async. + * + * For pre-bugfix #15172 Samba, the flush goes async and + * we should get NT_STATUS_INTERNAL_ERROR. + */ + torture_assert_ntstatus_equal_goto(tctx, + status, + NT_STATUS_INTERNAL_ERROR, + ret, + done, + "smb2_flush_recv didn't return " + "NT_STATUS_INTERNAL_ERROR.\n"); + } + status = smb2_close_recv(req[1], &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close_recv failed."); + + ZERO_STRUCT(fhandle); + + /* + * Do several more operations on the tree, spaced + * out by 1 sec sleeps to make sure the server didn't + * crash on the close. The sleeps are required to + * make test test for a crash reliable, as we ensure + * the pthread fsync internally finishes and accesses + * freed memory. Without them the test occassionally + * passes as we disconnect before the pthread fsync + * finishes. + */ + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ret = true; + + done: + + if (fhandle.data[0] != 0) { + smb2_util_close(tree, fhandle); + } + + smb2_util_unlink(tree, fname); + return ret; +} + +static bool test_compound_async_flush_flush(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle fhandle = { .data = { 0, 0 } }; + struct smb2_handle relhandle = { .data = { UINT64_MAX, UINT64_MAX } }; + struct smb2_flush fl1; + struct smb2_flush fl2; + const char *fname = "compound_async_flush_flush"; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = false; + + /* Start clean. */ + smb2_util_unlink(tree, fname); + + /* Create a file. */ + status = torture_smb2_testfile_access(tree, + fname, + &fhandle, + SEC_RIGHTS_FILE_ALL); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Now do a compound flush + flush handle. */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(fl1); + fl1.in.file.handle = fhandle; + + req[0] = smb2_flush_send(tree, &fl1); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_flush_send (1) failed\n"); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(fl2); + fl2.in.file.handle = relhandle; + + req[1] = smb2_flush_send(tree, &fl2); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_flush_send (2) failed\n"); + + status = smb2_flush_recv(req[0], &fl1); + /* + * On Windows, this flush will usually + * succeed as we have nothing to flush, + * so allow NT_STATUS_OK. Once bug #15172 + * is fixed Samba will do the flush synchronously + * so allow NT_STATUS_OK. + */ + if (!NT_STATUS_IS_OK(status)) { + /* + * If we didn't get NT_STATUS_OK, we *must* + * get NT_STATUS_INTERNAL_ERROR if the flush + * goes async. + * + * For pre-bugfix #15172 Samba, the flush goes async and + * we should get NT_STATUS_INTERNAL_ERROR. + */ + torture_assert_ntstatus_equal_goto(tctx, + status, + NT_STATUS_INTERNAL_ERROR, + ret, + done, + "smb2_flush_recv (1) didn't return " + "NT_STATUS_INTERNAL_ERROR.\n"); + } + + /* + * If the flush is the last entry in a compound, + * it should always succeed even if it goes async. + */ + status = smb2_flush_recv(req[1], &fl2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_flush_recv (2) failed."); + + status = smb2_util_close(tree, fhandle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed."); + ZERO_STRUCT(fhandle); + + /* + * Do several more operations on the tree, spaced + * out by 1 sec sleeps to make sure the server didn't + * crash on the close. The sleeps are required to + * make test test for a crash reliable, as we ensure + * the pthread fsync internally finishes and accesses + * freed memory. Without them the test occassionally + * passes as we disconnect before the pthread fsync + * finishes. + */ + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ret = true; + + done: + + if (fhandle.data[0] != 0) { + smb2_util_close(tree, fhandle); + } + + smb2_util_unlink(tree, fname); + return ret; +} + +struct torture_suite *torture_smb2_compound_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "compound"); + + torture_suite_add_1smb2_test(suite, "related1", test_compound_related1); + torture_suite_add_1smb2_test(suite, "related2", test_compound_related2); + torture_suite_add_1smb2_test(suite, "related3", + test_compound_related3); + torture_suite_add_1smb2_test(suite, "related4", + test_compound_related4); + torture_suite_add_1smb2_test(suite, "related5", + test_compound_related5); + torture_suite_add_1smb2_test(suite, "related6", + test_compound_related6); + torture_suite_add_1smb2_test(suite, "related7", + test_compound_related7); + torture_suite_add_1smb2_test(suite, "related8", + test_compound_related8); + torture_suite_add_1smb2_test(suite, "related9", + test_compound_related9); + torture_suite_add_1smb2_test(suite, "unrelated1", test_compound_unrelated1); + torture_suite_add_1smb2_test(suite, "invalid1", test_compound_invalid1); + torture_suite_add_1smb2_test(suite, "invalid2", test_compound_invalid2); + torture_suite_add_1smb2_test(suite, "invalid3", test_compound_invalid3); + torture_suite_add_1smb2_test( + suite, "invalid4", test_compound_invalid4); + torture_suite_add_1smb2_test(suite, "interim1", test_compound_interim1); + torture_suite_add_1smb2_test(suite, "interim2", test_compound_interim2); + torture_suite_add_1smb2_test(suite, "compound-break", test_compound_break); + torture_suite_add_1smb2_test(suite, "compound-padding", test_compound_padding); + torture_suite_add_1smb2_test(suite, "create-write-close", + test_compound_create_write_close); + + suite->description = talloc_strdup(suite, "SMB2-COMPOUND tests"); + + return suite; +} + +struct torture_suite *torture_smb2_compound_find_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "compound_find"); + + torture_suite_add_1smb2_test(suite, "compound_find_related", test_compound_find_related); + torture_suite_add_1smb2_test(suite, "compound_find_unrelated", test_compound_find_unrelated); + torture_suite_add_1smb2_test(suite, "compound_find_close", test_compound_find_close); + + suite->description = talloc_strdup(suite, "SMB2-COMPOUND-FIND tests"); + + return suite; +} + +struct torture_suite *torture_smb2_compound_async_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, + "compound_async"); + + torture_suite_add_1smb2_test(suite, "flush_close", + test_compound_async_flush_close); + torture_suite_add_1smb2_test(suite, "flush_flush", + test_compound_async_flush_flush); + + suite->description = talloc_strdup(suite, "SMB2-COMPOUND-ASYNC tests"); + + return suite; +} diff --git a/source4/torture/smb2/connect.c b/source4/torture/smb2/connect.c new file mode 100644 index 0000000..5a2b48b --- /dev/null +++ b/source4/torture/smb2/connect.c @@ -0,0 +1,257 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 connection operations + + Copyright (C) Andrew Tridgell 2005 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +/* + send a close +*/ +static NTSTATUS torture_smb2_close(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_handle handle) +{ + struct smb2_close io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + ZERO_STRUCT(io); + io.in.file.handle = handle; + io.in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION; + status = smb2_close(tree, &io); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "close failed - %s\n", nt_errstr(status)); + return status; + } + + if (DEBUGLVL(1)) { + torture_comment(tctx, "Close gave:\n"); + torture_comment(tctx, "create_time = %s\n", nt_time_string(tmp_ctx, io.out.create_time)); + torture_comment(tctx, "access_time = %s\n", nt_time_string(tmp_ctx, io.out.access_time)); + torture_comment(tctx, "write_time = %s\n", nt_time_string(tmp_ctx, io.out.write_time)); + torture_comment(tctx, "change_time = %s\n", nt_time_string(tmp_ctx, io.out.change_time)); + torture_comment(tctx, "alloc_size = %lld\n", (long long)io.out.alloc_size); + torture_comment(tctx, "size = %lld\n", (long long)io.out.size); + torture_comment(tctx, "file_attr = 0x%x\n", io.out.file_attr); + } + + talloc_free(tmp_ctx); + + return status; +} + + +/* + test writing +*/ +static NTSTATUS torture_smb2_write(struct torture_context *tctx, struct smb2_tree *tree, struct smb2_handle handle) +{ + struct smb2_write w; + struct smb2_read r; + struct smb2_flush f; + NTSTATUS status; + DATA_BLOB data; + int i; + uint32_t size = torture_setting_int(tctx, "smb2maxwrite", 64*1024); + + data = data_blob_talloc(tree, NULL, size); + if (size != data.length) { + torture_comment(tctx, "data_blob_talloc(%u) failed\n", (unsigned int)size); + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<data.length;i++) { + data.data[i] = i; + } + + ZERO_STRUCT(w); + w.in.file.handle = handle; + w.in.offset = 0; + w.in.data = data; + + status = smb2_write(tree, &w); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "write 1 failed - %s\n", nt_errstr(status)); + return status; + } + + torture_smb2_all_info(tctx, tree, handle); + + status = smb2_write(tree, &w); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "write 2 failed - %s\n", nt_errstr(status)); + return status; + } + + torture_smb2_all_info(tctx, tree, handle); + + ZERO_STRUCT(f); + f.in.file.handle = handle; + + status = smb2_flush(tree, &f); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "flush failed - %s\n", nt_errstr(status)); + return status; + } + + ZERO_STRUCT(r); + r.in.file.handle = handle; + r.in.length = data.length; + r.in.offset = 0; + + status = smb2_read(tree, tree, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "read failed - %s\n", nt_errstr(status)); + return status; + } + + if (data.length != r.out.data.length || + memcmp(data.data, r.out.data.data, data.length) != 0) { + torture_comment(tctx, "read data mismatch\n"); + return NT_STATUS_NET_WRITE_FAULT; + } + + return status; +} + + +/* + send a create +*/ +NTSTATUS torture_smb2_createfile(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + struct smb2_handle *handle) +{ + struct smb2_create io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = NTCREATEX_OPTIONS_WRITE_THROUGH; + io.in.fname = fname; + + status = smb2_create(tree, tmp_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + torture_comment(tctx, "create1 failed - %s\n", nt_errstr(status)); + return status; + } + + if (DEBUGLVL(1)) { + torture_comment(tctx, "Open gave:\n"); + torture_comment(tctx, "oplock_flags = 0x%x\n", io.out.oplock_level); + torture_comment(tctx, "create_action = 0x%x\n", io.out.create_action); + torture_comment(tctx, "create_time = %s\n", nt_time_string(tmp_ctx, io.out.create_time)); + torture_comment(tctx, "access_time = %s\n", nt_time_string(tmp_ctx, io.out.access_time)); + torture_comment(tctx, "write_time = %s\n", nt_time_string(tmp_ctx, io.out.write_time)); + torture_comment(tctx, "change_time = %s\n", nt_time_string(tmp_ctx, io.out.change_time)); + torture_comment(tctx, "alloc_size = %lld\n", (long long)io.out.alloc_size); + torture_comment(tctx, "size = %lld\n", (long long)io.out.size); + torture_comment(tctx, "file_attr = 0x%x\n", io.out.file_attr); + torture_comment(tctx, "handle = %016llx%016llx\n", + (long long)io.out.file.handle.data[0], + (long long)io.out.file.handle.data[1]); + } + + talloc_free(tmp_ctx); + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + + +/* + basic testing of SMB2 connection calls +*/ +bool torture_smb2_connect(struct torture_context *tctx) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_tree *tree; + struct smb2_request *req; + struct smb2_handle h1, h2; + NTSTATUS status; + bool ok; + + ok = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ok, "torture_smb2_connection failed"); + + smb2_util_unlink(tree, "test9.dat"); + + status = torture_smb2_createfile(tctx, tree, "test9.dat", &h1); + torture_assert_ntstatus_ok(tctx, status, "create failed"); + + status = torture_smb2_createfile(tctx, tree, "test9.dat", &h2); + torture_assert_ntstatus_ok(tctx, status, "create failed"); + + status = torture_smb2_write(tctx, tree, h1); + torture_assert_ntstatus_ok(tctx, status, "write failed"); + + status = torture_smb2_close(tctx, tree, h1); + torture_assert_ntstatus_ok(tctx, status, "close failed"); + + status = torture_smb2_close(tctx, tree, h2); + torture_assert_ntstatus_ok(tctx, status, "close failed"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_FILE_CLOSED, + "close should have closed the handle"); + + status = smb2_tdis(tree); + torture_assert_ntstatus_ok(tctx, status, "tdis failed"); + + status = smb2_tdis(tree); + torture_assert_ntstatus_equal(tctx, status, + NT_STATUS_NETWORK_NAME_DELETED, + "tdis should have closed the tcon"); + + status = smb2_logoff(tree->session); + torture_assert_ntstatus_ok(tctx, status, "logoff failed"); + + req = smb2_logoff_send(tree->session); + torture_assert_not_null(tctx, req, "smb2_logoff_send failed"); + + req->session = NULL; + + status = smb2_logoff_recv(req); + + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_USER_SESSION_DELETED, + "logoff should have disabled session"); + + status = smb2_keepalive(tree->session->transport); + torture_assert_ntstatus_ok(tctx, status, "keepalive failed"); + + talloc_free(mem_ctx); + + return true; +} diff --git a/source4/torture/smb2/create.c b/source4/torture/smb2/create.c new file mode 100644 index 0000000..cdd5436 --- /dev/null +++ b/source4/torture/smb2/create.c @@ -0,0 +1,4076 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 create test suite + + Copyright (C) Andrew Tridgell 2008 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb/smbXcli_base.h" +#include "torture/torture.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" + +#include "system/filesys.h" +#include "auth/credentials/credentials.h" +#include "lib/cmdline/cmdline.h" +#include "librpc/gen_ndr/security.h" +#include "lib/events/events.h" + +#define FNAME "test_create.dat" +#define DNAME "smb2_open" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + return false; \ + }} while (0) + +#define CHECK_EQUAL(v, correct) do { \ + if (v != correct) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect value for %s 0x%08llx - " \ + "should be 0x%08llx\n", \ + __location__, #v, \ + (unsigned long long)v, \ + (unsigned long long)correct); \ + return false; \ + }} while (0) + +#define CHECK_TIME(t, field) do { \ + time_t t1, t2; \ + finfo.all_info.level = RAW_FILEINFO_ALL_INFORMATION; \ + finfo.all_info.in.file.handle = h1; \ + status = smb2_getinfo_file(tree, tctx, &finfo); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + t1 = t & ~1; \ + t2 = nt_time_to_unix(finfo.all_info.out.field) & ~1; \ + if (abs(t1-t2) > 2) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) wrong time for field %s %s - %s\n", \ + __location__, #field, \ + timestring(tctx, t1), \ + timestring(tctx, t2)); \ + dump_all_info(tctx, &finfo); \ + ret = false; \ + }} while (0) + +#define CHECK_NTTIME(t, field) do { \ + NTTIME t2; \ + finfo.all_info.level = RAW_FILEINFO_ALL_INFORMATION; \ + finfo.all_info.in.file.handle = h1; \ + status = smb2_getinfo_file(tree, tctx, &finfo); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + t2 = finfo.all_info.out.field; \ + if (llabs((int64_t)(t-t2)) > 20000) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) wrong time for field %s %s - %s\n", \ + __location__, #field, \ + nt_time_string(tctx, t), \ + nt_time_string(tctx, t2)); \ + dump_all_info(tctx, &finfo); \ + ret = false; \ + }} while (0) + +#define CHECK_ALL_INFO(v, field) do { \ + finfo.all_info.level = RAW_FILEINFO_ALL_INFORMATION; \ + finfo.all_info.in.file.handle = h1; \ + status = smb2_getinfo_file(tree, tctx, &finfo); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + if ((v) != (finfo.all_info.out.field)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) wrong value for field %s 0x%x - 0x%x\n", \ + __location__, #field, (int)v,\ + (int)(finfo.all_info.out.field)); \ + dump_all_info(tctx, &finfo); \ + ret = false; \ + }} while (0) + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) wrong value for %s 0x%x - should be 0x%x\n", \ + __location__, #v, (int)(v), (int)correct); \ + ret = false; \ + }} while (0) + +#define SET_ATTRIB(sattrib) do { \ + union smb_setfileinfo sfinfo; \ + ZERO_STRUCT(sfinfo.basic_info.in); \ + sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION; \ + sfinfo.basic_info.in.file.handle = h1; \ + sfinfo.basic_info.in.attrib = sattrib; \ + status = smb2_setinfo_file(tree, &sfinfo); \ + if (!NT_STATUS_IS_OK(status)) { \ + torture_comment(tctx, \ + "(%s) Failed to set attrib 0x%x on %s\n", \ + __location__, (unsigned int)(sattrib), fname); \ + }} while (0) + +/* + stress testing keepalive iops + */ + +struct test_smb2_bench_echo_conn; +struct test_smb2_bench_echo_loop; + +struct test_smb2_bench_echo_state { + struct torture_context *tctx; + size_t num_conns; + struct test_smb2_bench_echo_conn *conns; + size_t num_loops; + struct test_smb2_bench_echo_loop *loops; + struct timeval starttime; + int timecount; + int timelimit; + uint64_t num_finished; + double total_latency; + double min_latency; + double max_latency; + bool ok; + bool stop; +}; + +struct test_smb2_bench_echo_conn { + struct test_smb2_bench_echo_state *state; + int idx; + struct smb2_tree *tree; +}; + +struct test_smb2_bench_echo_loop { + struct test_smb2_bench_echo_state *state; + struct test_smb2_bench_echo_conn *conn; + int idx; + struct tevent_immediate *im; + struct tevent_req *req; + struct timeval starttime; + uint64_t num_started; + uint64_t num_finished; + double total_latency; + double min_latency; + double max_latency; + NTSTATUS error; +}; + +static void test_smb2_bench_echo_loop_do( + struct test_smb2_bench_echo_loop *loop); + +static void test_smb2_bench_echo_loop_start(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data) +{ + struct test_smb2_bench_echo_loop *loop = + (struct test_smb2_bench_echo_loop *) + private_data; + + test_smb2_bench_echo_loop_do(loop); +} + +static void test_smb2_bench_echo_loop_done(struct tevent_req *req); + +static void test_smb2_bench_echo_loop_do( + struct test_smb2_bench_echo_loop *loop) +{ + struct test_smb2_bench_echo_state *state = loop->state; + + loop->num_started += 1; + loop->starttime = timeval_current(); + loop->req = smb2cli_echo_send(state->loops, + state->tctx->ev, + loop->conn->tree->session->transport->conn, + 1000); + torture_assert_goto(state->tctx, loop->req != NULL, + state->ok, asserted, "smb2_create_send"); + + tevent_req_set_callback(loop->req, + test_smb2_bench_echo_loop_done, + loop); + return; +asserted: + state->stop = true; +} + +static void test_smb2_bench_echo_loop_done(struct tevent_req *req) +{ + struct test_smb2_bench_echo_loop *loop = + (struct test_smb2_bench_echo_loop *) + _tevent_req_callback_data(req); + struct test_smb2_bench_echo_state *state = loop->state; + double latency = timeval_elapsed(&loop->starttime); + TALLOC_CTX *frame = talloc_stackframe(); + + torture_assert_goto(state->tctx, loop->req == req, + state->ok, asserted, __location__); + loop->error = smb2cli_echo_recv(req); + torture_assert_ntstatus_ok_goto(state->tctx, loop->error, + state->ok, asserted, __location__); + SMB_ASSERT(latency >= 0.000001); + + if (loop->num_finished == 0) { + /* first round */ + loop->min_latency = latency; + loop->max_latency = latency; + } + + loop->num_finished += 1; + loop->total_latency += latency; + + if (latency < loop->min_latency) { + loop->min_latency = latency; + } + + if (latency > loop->max_latency) { + loop->max_latency = latency; + } + + TALLOC_FREE(frame); + test_smb2_bench_echo_loop_do(loop); + return; +asserted: + state->stop = true; + TALLOC_FREE(frame); +} + +static void test_smb2_bench_echo_progress(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct test_smb2_bench_echo_state *state = + (struct test_smb2_bench_echo_state *)private_data; + uint64_t num_echos = 0; + double total_echo_latency = 0; + double min_echo_latency = 0; + double max_echo_latency = 0; + double avs_echo_latency = 0; + size_t i; + + state->timecount += 1; + + for (i=0;i<state->num_loops;i++) { + struct test_smb2_bench_echo_loop *loop = + &state->loops[i]; + + num_echos += loop->num_finished; + total_echo_latency += loop->total_latency; + if (min_echo_latency == 0.0 && loop->min_latency != 0.0) { + min_echo_latency = loop->min_latency; + } + if (loop->min_latency < min_echo_latency) { + min_echo_latency = loop->min_latency; + } + if (max_echo_latency == 0.0) { + max_echo_latency = loop->max_latency; + } + if (loop->max_latency > max_echo_latency) { + max_echo_latency = loop->max_latency; + } + loop->num_finished = 0; + loop->total_latency = 0.0; + } + + state->num_finished += num_echos; + state->total_latency += total_echo_latency; + if (state->min_latency == 0.0 && min_echo_latency != 0.0) { + state->min_latency = min_echo_latency; + } + if (min_echo_latency < state->min_latency) { + state->min_latency = min_echo_latency; + } + if (state->max_latency == 0.0) { + state->max_latency = max_echo_latency; + } + if (max_echo_latency > state->max_latency) { + state->max_latency = max_echo_latency; + } + + if (state->timecount < state->timelimit) { + te = tevent_add_timer(state->tctx->ev, + state, + timeval_current_ofs(1, 0), + test_smb2_bench_echo_progress, + state); + torture_assert_goto(state->tctx, te != NULL, + state->ok, asserted, "tevent_add_timer"); + + if (!torture_setting_bool(state->tctx, "progress", true)) { + return; + } + + avs_echo_latency = total_echo_latency / num_echos; + + torture_comment(state->tctx, + "%.2f second: " + "echo[num/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f] \r", + timeval_elapsed(&state->starttime), + (unsigned long long)num_echos, + avs_echo_latency, + min_echo_latency, + max_echo_latency); + return; + } + + avs_echo_latency = state->total_latency / state->num_finished; + num_echos = state->num_finished / state->timelimit; + + torture_comment(state->tctx, + "%.2f second: " + "echo[num/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f]\n", + timeval_elapsed(&state->starttime), + (unsigned long long)num_echos, + avs_echo_latency, + state->min_latency, + state->max_latency); + +asserted: + state->stop = true; +} + +static bool test_smb2_bench_echo(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct test_smb2_bench_echo_state *state = NULL; + bool ret = true; + int torture_nprocs = torture_setting_int(tctx, "nprocs", 4); + int torture_qdepth = torture_setting_int(tctx, "qdepth", 1); + size_t i; + size_t li = 0; + int timelimit = torture_setting_int(tctx, "timelimit", 10); + struct tevent_timer *te = NULL; + uint32_t timeout_msec; + + state = talloc_zero(tctx, struct test_smb2_bench_echo_state); + torture_assert(tctx, state != NULL, __location__); + state->tctx = tctx; + state->num_conns = torture_nprocs; + state->conns = talloc_zero_array(state, + struct test_smb2_bench_echo_conn, + state->num_conns); + torture_assert(tctx, state->conns != NULL, __location__); + state->num_loops = torture_nprocs * torture_qdepth; + state->loops = talloc_zero_array(state, + struct test_smb2_bench_echo_loop, + state->num_loops); + torture_assert(tctx, state->loops != NULL, __location__); + state->ok = true; + state->timelimit = MAX(timelimit, 1); + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + torture_comment(tctx, "Opening %zu connections\n", state->num_conns); + + for (i=0;i<state->num_conns;i++) { + struct smb2_tree *ct = NULL; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + size_t pcli; + + state->conns[i].state = state; + state->conns[i].idx = i; + + if (!torture_smb2_connection(tctx, &ct)) { + torture_comment(tctx, "Failed opening %zu/%zu connections\n", i, state->num_conns); + return false; + } + state->conns[i].tree = talloc_steal(state->conns, ct); + + smb2cli_conn_set_max_credits(ct->session->transport->conn, 8192); + smb2cli_ioctl(ct->session->transport->conn, + timeout_msec, + ct->session->smbXcli, + ct->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + UINT32_MAX, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + ct, + &out_input_buffer, + &out_output_buffer); + torture_assert(tctx, + smbXcli_conn_is_connected(ct->session->transport->conn), + "smbXcli_conn_is_connected"); + + for (pcli = 0; pcli < torture_qdepth; pcli++) { + struct test_smb2_bench_echo_loop *loop = &state->loops[li]; + + loop->idx = li++; + loop->state = state; + loop->conn = &state->conns[i]; + loop->im = tevent_create_immediate(state->loops); + torture_assert(tctx, loop->im != NULL, __location__); + + tevent_schedule_immediate(loop->im, + tctx->ev, + test_smb2_bench_echo_loop_start, + loop); + } + } + + torture_comment(tctx, "Opened %zu connections with qdepth=%d => %zu loops\n", + state->num_conns, torture_qdepth, state->num_loops); + + torture_comment(tctx, "Running for %d seconds\n", state->timelimit); + + state->starttime = timeval_current(); + + te = tevent_add_timer(tctx->ev, + state, + timeval_current_ofs(1, 0), + test_smb2_bench_echo_progress, + state); + torture_assert(tctx, te != NULL, __location__); + + while (!state->stop) { + int rc = tevent_loop_once(tctx->ev); + torture_assert_int_equal(tctx, rc, 0, "tevent_loop_once"); + } + + torture_comment(tctx, "%.2f seconds\n", timeval_elapsed(&state->starttime)); + TALLOC_FREE(state); + return ret; +} + +/* + test some interesting combinations found by gentest + */ +static bool test_create_gentest(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + uint32_t access_mask, file_attributes_set; + uint32_t ok_mask, not_supported_mask, invalid_parameter_mask; + uint32_t not_a_directory_mask, unexpected_mask; + union smb_fileinfo q; + + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + io.in.create_options = 0xF0000000; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + io.in.create_options = 0; + + io.in.file_attributes = FILE_ATTRIBUTE_DEVICE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + io.in.file_attributes = FILE_ATTRIBUTE_VOLUME; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.file_attributes = FILE_ATTRIBUTE_VOLUME; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.desired_access = 0x08000000; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + io.in.desired_access = 0x04000000; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + ok_mask = 0; + not_supported_mask = 0; + invalid_parameter_mask = 0; + not_a_directory_mask = 0; + unexpected_mask = 0; + { + int i; + for (i=0;i<32;i++) { + io.in.create_options = (uint32_t)1<<i; + if (io.in.create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) { + continue; + } + status = smb2_create(tree, tctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + not_supported_mask |= 1<<i; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + invalid_parameter_mask |= 1<<i; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY)) { + not_a_directory_mask |= 1<<i; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + ok_mask |= 1<<i; + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + } else { + unexpected_mask |= 1<<i; + torture_comment(tctx, + "create option 0x%08x returned %s\n", + 1<<i, nt_errstr(status)); + } + } + } + io.in.create_options = 0; + + CHECK_EQUAL(ok_mask, 0x00efcf7e); + CHECK_EQUAL(not_a_directory_mask, 0x00000001); + CHECK_EQUAL(not_supported_mask, 0x00102080); + CHECK_EQUAL(invalid_parameter_mask, 0xff000000); + CHECK_EQUAL(unexpected_mask, 0x00000000); + + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.file_attributes = 0; + access_mask = 0; + { + int i; + for (i=0;i<32;i++) { + io.in.desired_access = (uint32_t)1<<i; + status = smb2_create(tree, tctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(status, NT_STATUS_PRIVILEGE_NOT_HELD)) { + access_mask |= io.in.desired_access; + } else { + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + } + } + } + + if (TARGET_IS_WIN7(tctx)) { + CHECK_EQUAL(access_mask, 0x0de0fe00); + } else if (torture_setting_bool(tctx, "samba4", false)) { + CHECK_EQUAL(access_mask, 0x0cf0fe00); + } else { + CHECK_EQUAL(access_mask, 0x0df0fe00); + } + + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = 0; + ok_mask = 0; + invalid_parameter_mask = 0; + unexpected_mask = 0; + file_attributes_set = 0; + { + int i; + for (i=0;i<32;i++) { + io.in.file_attributes = (uint32_t)1<<i; + if (io.in.file_attributes & FILE_ATTRIBUTE_ENCRYPTED) { + continue; + } + smb2_deltree(tree, FNAME); + status = smb2_create(tree, tctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + invalid_parameter_mask |= 1<<i; + } else if (NT_STATUS_IS_OK(status)) { + uint32_t expected; + ok_mask |= 1<<i; + + expected = (io.in.file_attributes | FILE_ATTRIBUTE_ARCHIVE) & 0x00005127; + io.out.file_attr &= ~FILE_ATTRIBUTE_NONINDEXED; + CHECK_EQUAL(io.out.file_attr, expected); + file_attributes_set |= io.out.file_attr; + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + } else { + unexpected_mask |= 1<<i; + torture_comment(tctx, + "file attribute 0x%08x returned %s\n", + 1<<i, nt_errstr(status)); + } + } + } + + CHECK_EQUAL(ok_mask, 0x00003fb7); + CHECK_EQUAL(invalid_parameter_mask, 0xffff8048); + CHECK_EQUAL(unexpected_mask, 0x00000000); + CHECK_EQUAL(file_attributes_set, 0x00001127); + + smb2_deltree(tree, FNAME); + + /* + * Standalone servers doesn't support encryption + */ + io.in.file_attributes = FILE_ATTRIBUTE_ENCRYPTED; + status = smb2_create(tree, tctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, + "FILE_ATTRIBUTE_ENCRYPTED returned %s\n", + nt_errstr(status)); + } else { + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_EQUAL(io.out.file_attr, (FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_ARCHIVE)); + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + } + + smb2_deltree(tree, FNAME); + + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = FNAME ":stream1"; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + io.in.fname = FNAME; + io.in.file_attributes = 0x8040; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + io.in.fname = FNAME; + io.in.file_attributes = 0; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA; + io.in.query_maximal_access = true; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_EQUAL(io.out.maximal_access, 0x001f01ff); + + q.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; + q.access_information.in.file.handle = io.out.file.handle; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_EQUAL(q.access_information.out.access_flags, io.in.desired_access); + + io.in.file_attributes = 0; + io.in.desired_access = 0; + io.in.query_maximal_access = false; + io.in.share_access = 0; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + smb2_deltree(tree, FNAME); + + return true; +} + + +/* + try the various request blobs + */ +static bool test_create_blob(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + + smb2_deltree(tree, FNAME); + + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing alloc size\n"); + /* FIXME We use 1M cause that's the rounded size of Samba. + * We should ask the server for the cluser size and calulate it + * correctly. */ + io.in.alloc_size = 0x00100000; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_EQUAL(io.out.alloc_size, io.in.alloc_size); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing durable open\n"); + io.in.durable_open = true; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing query maximal access\n"); + io.in.query_maximal_access = true; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_EQUAL(io.out.maximal_access, 0x001f01ff); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing timewarp\n"); + io.in.timewarp = 10000; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + io.in.timewarp = 0; + + torture_comment(tctx, "Testing query_on_disk\n"); + io.in.query_on_disk_id = true; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing unknown tag\n"); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "FooO", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing bad tag length 0\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "x", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + torture_comment(tctx, "Testing bad tag length 1\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "x", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + torture_comment(tctx, "Testing bad tag length 2\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + torture_comment(tctx, "Testing bad tag length 3\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + torture_comment(tctx, "Testing tag length 4\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing tag length 5\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing tag length 6\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxxxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing tag length 7\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxxxxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing tag length 8\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxxxxxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing tag length 16\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxxxxxxxxxxxxxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing tag length 17\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxxxxxxxxxxxxxxx", data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "Testing tag length 34\n"); + ZERO_STRUCT(io.in.blobs); + status = smb2_create_blob_add(tctx, &io.in.blobs, + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + data_blob(NULL, 0)); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_deltree(tree, FNAME); + + return true; +} + +#define FAIL_UNLESS(__cond) \ + do { \ + if (__cond) {} else { \ + torture_result(tctx, TORTURE_FAIL, "%s) condition violated: %s\n", \ + __location__, #__cond); \ + ret = false; goto done; \ + } \ + } while(0) + +/* + try creating with acls + */ +static bool test_create_acl_ext(struct torture_context *tctx, struct smb2_tree *tree, bool test_dir) +{ + bool ret = true; + struct smb2_create io; + NTSTATUS status; + struct security_ace ace; + struct security_descriptor *sd; + struct dom_sid *test_sid; + union smb_fileinfo q = {}; + uint32_t attrib = + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_SYSTEM | + (test_dir ? FILE_ATTRIBUTE_DIRECTORY : 0); + NTSTATUS (*delete_func)(struct smb2_tree *, const char *) = + test_dir ? smb2_util_rmdir : smb2_util_unlink; + + ZERO_STRUCT(ace); + + smb2_deltree(tree, FNAME); + + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = NTCREATEX_OPTIONS_ASYNC_ALERT | 0x00200000 | + (test_dir ? NTCREATEX_OPTIONS_DIRECTORY : + (NTCREATEX_OPTIONS_NON_DIRECTORY_FILE)); + + io.in.fname = FNAME; + + torture_comment(tctx, "basic create\n"); + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = io.out.file.handle; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd = q.query_secdesc.out.sd; + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + status = delete_func(tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "adding a new ACE\n"); + test_sid = dom_sid_parse_talloc(tctx, SID_NT_AUTHENTICATED_USERS); + + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.access_mask = SEC_STD_ALL; + ace.trustee = *test_sid; + + status = security_descriptor_dacl_add(sd, &ace); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "creating a file with an initial ACL\n"); + + io.in.sec_desc = sd; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + FAIL_UNLESS(smb2_util_verify_sd(tctx, tree, io.out.file.handle, sd)); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + status = delete_func(tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "creating with attributes\n"); + + io.in.sec_desc = NULL; + io.in.file_attributes = attrib; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + FAIL_UNLESS(smb2_util_verify_attrib(tctx, tree, io.out.file.handle, attrib)); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + status = delete_func(tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "creating with attributes and ACL\n"); + + io.in.sec_desc = sd; + io.in.file_attributes = attrib; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + FAIL_UNLESS(smb2_util_verify_sd(tctx, tree, io.out.file.handle, sd)); + FAIL_UNLESS(smb2_util_verify_attrib(tctx, tree, io.out.file.handle, attrib)); + + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + status = delete_func(tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "creating with attributes, ACL and owner\n"); + sd = security_descriptor_dacl_create(tctx, + 0, SID_WORLD, SID_BUILTIN_USERS, + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + + io.in.sec_desc = sd; + io.in.file_attributes = attrib; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + FAIL_UNLESS(smb2_util_verify_sd(tctx, tree, io.out.file.handle, sd)); + FAIL_UNLESS(smb2_util_verify_attrib(tctx, tree, io.out.file.handle, attrib)); + + done: + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + status = delete_func(tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + return ret; +} + +/* + test SMB2 open +*/ +static bool test_smb2_open(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_open io; + union smb_fileinfo finfo; + const char *fname = DNAME "\\torture_ntcreatex.txt"; + const char *dname = DNAME "\\torture_ntcreatex.dir"; + NTSTATUS status; + struct smb2_handle h = {{0}}; + struct smb2_handle h1 = {{0}}; + bool ret = true; + size_t i; + struct { + uint32_t create_disp; + bool with_file; + NTSTATUS correct_status; + } open_funcs[] = { + { NTCREATEX_DISP_SUPERSEDE, true, NT_STATUS_OK }, + { NTCREATEX_DISP_SUPERSEDE, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { NTCREATEX_DISP_CREATE, true, NT_STATUS_OBJECT_NAME_COLLISION }, + { NTCREATEX_DISP_CREATE, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN_IF, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OPEN_IF, false, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE, false, NT_STATUS_OBJECT_NAME_NOT_FOUND }, + { NTCREATEX_DISP_OVERWRITE_IF, true, NT_STATUS_OK }, + { NTCREATEX_DISP_OVERWRITE_IF, false, NT_STATUS_OK }, + { 6, true, NT_STATUS_INVALID_PARAMETER }, + { 6, false, NT_STATUS_INVALID_PARAMETER }, + }; + + torture_comment(tctx, "Checking SMB2 Open\n"); + + smb2_util_unlink(tree, fname); + smb2_util_rmdir(tree, dname); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(io.smb2); + /* reasonable default parameters */ + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 1024*1024; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* test the create disposition */ + for (i=0; i<ARRAY_SIZE(open_funcs); i++) { + if (open_funcs[i].with_file) { + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status= smb2_create(tree, tctx, &(io.smb2)); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, + "Failed to create file %s status %s %zu\n", + fname, nt_errstr(status), i); + + ret = false; + goto done; + } + smb2_util_close(tree, io.smb2.out.file.handle); + } + io.smb2.in.create_disposition = open_funcs[i].create_disp; + status = smb2_create(tree, tctx, &(io.smb2)); + if (!NT_STATUS_EQUAL(status, open_funcs[i].correct_status)) { + torture_comment(tctx, + "(%s) incorrect status %s should be %s (i=%zu " + "with_file=%d open_disp=%d)\n", + __location__, nt_errstr(status), + nt_errstr(open_funcs[i].correct_status), + i, (int)open_funcs[i].with_file, + (int)open_funcs[i].create_disp); + + ret = false; + goto done; + } + if (NT_STATUS_IS_OK(status) || open_funcs[i].with_file) { + smb2_util_close(tree, io.smb2.out.file.handle); + smb2_util_unlink(tree, fname); + } + } + + /* basic field testing */ + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + + status = smb2_create(tree, tctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + CHECK_VAL(io.smb2.out.oplock_level, 0); + CHECK_VAL(io.smb2.out.create_action, NTCREATEX_ACTION_CREATED); + CHECK_NTTIME(io.smb2.out.create_time, create_time); + CHECK_NTTIME(io.smb2.out.access_time, access_time); + CHECK_NTTIME(io.smb2.out.write_time, write_time); + CHECK_NTTIME(io.smb2.out.change_time, change_time); + CHECK_ALL_INFO(io.smb2.out.file_attr, attrib); + CHECK_ALL_INFO(io.smb2.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.smb2.out.size, size); + + /* check fields when the file already existed */ + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + + status = smb2_create_complex_file(tctx, tree, fname, &h1); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h1); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, tctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + CHECK_VAL(io.smb2.out.oplock_level, 0); + CHECK_VAL(io.smb2.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_NTTIME(io.smb2.out.create_time, create_time); + CHECK_NTTIME(io.smb2.out.access_time, access_time); + CHECK_NTTIME(io.smb2.out.write_time, write_time); + CHECK_NTTIME(io.smb2.out.change_time, change_time); + CHECK_ALL_INFO(io.smb2.out.file_attr, attrib); + CHECK_ALL_INFO(io.smb2.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.smb2.out.size, size); + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + + /* create a directory */ + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_options = 0; + io.smb2.in.fname = dname; + fname = dname; + + smb2_util_rmdir(tree, fname); + smb2_util_unlink(tree, fname); + + io.smb2.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + status = smb2_create(tree, tctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + CHECK_VAL(io.smb2.out.oplock_level, 0); + CHECK_VAL(io.smb2.out.create_action, NTCREATEX_ACTION_CREATED); + CHECK_NTTIME(io.smb2.out.create_time, create_time); + CHECK_NTTIME(io.smb2.out.access_time, access_time); + CHECK_NTTIME(io.smb2.out.write_time, write_time); + CHECK_NTTIME(io.smb2.out.change_time, change_time); + CHECK_ALL_INFO(io.smb2.out.file_attr, attrib); + CHECK_VAL(io.smb2.out.file_attr & ~FILE_ATTRIBUTE_NONINDEXED, + FILE_ATTRIBUTE_DIRECTORY); + CHECK_ALL_INFO(io.smb2.out.alloc_size, alloc_size); + CHECK_ALL_INFO(io.smb2.out.size, size); + CHECK_VAL(io.smb2.out.size, 0); + CHECK_VAL(io.smb2.out.alloc_size, 0); + smb2_util_unlink(tree, fname); + +done: + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + return ret; +} + +/* + test with an already opened and byte range locked file +*/ + +static bool test_smb2_open_brlocked(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_open io, io1; + union smb_lock io2; + struct smb2_lock_element lock[1]; + const char *fname = DNAME "\\torture_ntcreatex.txt"; + NTSTATUS status; + bool ret = true; + struct smb2_handle h; + char b = 42; + + torture_comment(tctx, + "Testing SMB2 open with a byte range locked file\n"); + + smb2_util_unlink(tree, fname); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.desired_access = 0x2019f; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + io.smb2.in.security_flags = SMB2_SECURITY_DYNAMIC_TRACKING; + io.smb2.in.fname = fname; + + status = smb2_create(tree, tctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, io.smb2.out.file.handle, &b, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(io2.smb2); + io2.smb2.level = RAW_LOCK_SMB2; + io2.smb2.in.file.handle = io.smb2.out.file.handle; + io2.smb2.in.lock_count = 1; + + ZERO_STRUCT(lock); + lock[0].offset = 0; + lock[0].length = 1; + lock[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + io2.smb2.in.locks = &lock[0]; + status = smb2_lock(tree, &(io2.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(io1.smb2); + io1.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io1.smb2.in.desired_access = 0x20196; + io1.smb2.in.alloc_size = 0; + io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io1.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io1.smb2.in.create_options = 0; + io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + io1.smb2.in.security_flags = SMB2_SECURITY_DYNAMIC_TRACKING; + io1.smb2.in.fname = fname; + + status = smb2_create(tree, tctx, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, io.smb2.out.file.handle); + smb2_util_close(tree, io1.smb2.out.file.handle); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + return ret; +} + +/* A little torture test to expose a race condition in Samba 3.0.20 ... :-) */ + +static bool test_smb2_open_multi(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "test_oplock.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_tree **trees; + struct smb2_request **requests; + union smb_open *ios; + int i, num_files = 3; + int num_ok = 0; + int num_collision = 0; + + torture_comment(tctx, + "Testing SMB2 Open with multiple connections\n"); + trees = talloc_array(tctx, struct smb2_tree *, num_files); + requests = talloc_array(tctx, struct smb2_request *, num_files); + ios = talloc_array(tctx, union smb_open, num_files); + if ((tctx->ev == NULL) || (trees == NULL) || (requests == NULL) || + (ios == NULL)) { + torture_comment(tctx, ("talloc failed\n")); + ret = false; + goto done; + } + + tree->session->transport->options.request_timeout = 60; + + for (i=0; i<num_files; i++) { + if (!torture_smb2_connection(tctx, &(trees[i]))) { + torture_comment(tctx, + "Could not open %d'th connection\n", i); + ret = false; + goto done; + } + trees[i]->session->transport->options.request_timeout = 60; + } + + /* cleanup */ + smb2_util_unlink(tree, fname); + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + io.smb2.in.create_flags = 0; + + for (i=0; i<num_files; i++) { + ios[i] = io; + requests[i] = smb2_create_send(trees[i], &(ios[i].smb2)); + if (requests[i] == NULL) { + torture_comment(tctx, + "could not send %d'th request\n", i); + ret = false; + goto done; + } + } + + torture_comment(tctx, "waiting for replies\n"); + while (1) { + bool unreplied = false; + for (i=0; i<num_files; i++) { + if (requests[i] == NULL) { + continue; + } + if (requests[i]->state < SMB2_REQUEST_DONE) { + unreplied = true; + break; + } + status = smb2_create_recv(requests[i], tctx, + &(ios[i].smb2)); + + torture_comment(tctx, + "File %d returned status %s\n", i, + nt_errstr(status)); + + if (NT_STATUS_IS_OK(status)) { + num_ok += 1; + } + + if (NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_COLLISION)) { + num_collision += 1; + } + + requests[i] = NULL; + } + if (!unreplied) { + break; + } + + if (tevent_loop_once(tctx->ev) != 0) { + torture_comment(tctx, "tevent_loop_once failed\n"); + ret = false; + goto done; + } + } + + if ((num_ok != 1) || (num_ok + num_collision != num_files)) { + ret = false; + } +done: + smb2_deltree(tree, fname); + + return ret; +} + +/* + test opening for delete on a read-only attribute file. +*/ + +static bool test_smb2_open_for_delete(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_open io; + union smb_fileinfo finfo; + const char *fname = DNAME "\\torture_open_for_delete.txt"; + NTSTATUS status; + struct smb2_handle h, h1; + bool ret = true; + + torture_comment(tctx, + "Checking SMB2_OPEN for delete on a readonly file.\n"); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, fname); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + /* reasonable default parameters */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.alloc_size = 0; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_READONLY; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* Create the readonly file. */ + + status = smb2_create(tree, tctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + CHECK_VAL(io.smb2.out.oplock_level, 0); + io.smb2.in.create_options = 0; + CHECK_VAL(io.smb2.out.create_action, NTCREATEX_ACTION_CREATED); + CHECK_ALL_INFO(io.smb2.out.file_attr, attrib); + smb2_util_close(tree, h1); + + /* Now try and open for delete only - should succeed. */ + io.smb2.in.desired_access = SEC_STD_DELETE; + io.smb2.in.file_attributes = 0; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, tctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, io.smb2.out.file.handle); + + /* Clear readonly flag to allow file deletion */ + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE; + status = smb2_create(tree, tctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + SET_ATTRIB(FILE_ATTRIBUTE_ARCHIVE); + smb2_util_close(tree, h1); + + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + return ret; +} + +/* + test SMB2 open with a leading slash on the path. + Trying to create a directory with a leading slash + should give NT_STATUS_INVALID_PARAMETER error +*/ +static bool test_smb2_leading_slash(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_open io; + const char *dnameslash = "\\"DNAME; + NTSTATUS status; + bool ret = true; + + torture_comment(tctx, + "Trying to create a directory with leading slash on path\n"); + smb2_deltree(tree, dnameslash); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.oplock_level = 0; + io.smb2.in.desired_access = SEC_RIGHTS_DIR_ALL; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.fname = dnameslash; + + status = smb2_create(tree, tree, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + smb2_deltree(tree, dnameslash); + return ret; +} + +/* + test SMB2 open with an invalid impersonation level. + Should give NT_STATUS_BAD_IMPERSONATION_LEVEL error +*/ +static bool test_smb2_impersonation_level(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_open io; + const char *fname = DNAME "\\torture_invalid_impersonation_level.txt"; + NTSTATUS status; + struct smb2_handle h; + bool ret = true; + + torture_comment(tctx, + "Testing SMB2 open with an invalid impersonation level.\n"); + + smb2_util_unlink(tree, fname); + smb2_util_rmdir(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = 0x12345678; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + io.smb2.in.create_flags = 0; + + status = smb2_create(tree, tree, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_BAD_IMPERSONATION_LEVEL); + + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + return ret; +} + +static bool test_create_acl_file(struct torture_context *tctx, + struct smb2_tree *tree) +{ + torture_comment(tctx, "Testing nttrans create with sec_desc on files\n"); + + return test_create_acl_ext(tctx, tree, false); +} + +static bool test_create_acl_dir(struct torture_context *tctx, + struct smb2_tree *tree) +{ + torture_comment(tctx, "Testing nttrans create with sec_desc on directories\n"); + + return test_create_acl_ext(tctx, tree, true); +} + +#define CHECK_ACCESS_FLAGS(_fh, flags) do { \ + union smb_fileinfo _q; \ + _q.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; \ + _q.access_information.in.file.handle = (_fh); \ + status = smb2_getinfo_file(tree, tctx, &_q); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + if (_q.access_information.out.access_flags != (flags)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect access_flags 0x%08x - should be 0x%08x\n", \ + __location__, _q.access_information.out.access_flags, (flags)); \ + ret = false; \ + goto done; \ + } \ +} while (0) + +/* + * Test creating a file with a NULL DACL. + */ +static bool test_create_null_dacl(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + const char *fname = "nulldacl.txt"; + bool ret = true; + struct smb2_handle handle; + union smb_fileinfo q; + union smb_setfileinfo s; + struct security_descriptor *sd = security_descriptor_initialise(tctx); + struct security_acl dacl; + + torture_comment(tctx, "TESTING SEC_DESC WITH A NULL DACL\n"); + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = SEC_STD_READ_CONTROL | SEC_STD_WRITE_DAC + | SEC_STD_WRITE_OWNER; + io.in.create_options = 0; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = fname; + io.in.sec_desc = sd; + /* XXX create_options ? */ + io.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + + torture_comment(tctx, "creating a file with a empty sd\n"); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Testing the created DACL, + * the server should add the inherited DACL + * when SEC_DESC_DACL_PRESENT isn't specified + */ + if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { + ret = false; + torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); + } + if (q.query_secdesc.out.sd->dacl == NULL) { + ret = false; + torture_fail_goto(tctx, done, "no DACL has been created on the server!\n"); + } + + torture_comment(tctx, "set NULL DACL\n"); + sd->type |= SEC_DESC_DACL_PRESENT; + + s.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + s.set_secdesc.in.file.handle = handle; + s.set_secdesc.in.secinfo_flags = SECINFO_DACL; + s.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &s); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "get the sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Testing the modified DACL */ + if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { + ret = false; + torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); + } + if (q.query_secdesc.out.sd->dacl != NULL) { + ret = false; + torture_fail_goto(tctx, done, "DACL has been created on the server!\n"); + } + + io.in.create_disposition = NTCREATEX_DISP_OPEN; + + torture_comment(tctx, "try open for read control\n"); + io.in.desired_access = SEC_STD_READ_CONTROL; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_STD_READ_CONTROL); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "try open for write\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_FILE_WRITE_DATA); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "try open for read\n"); + io.in.desired_access = SEC_FILE_READ_DATA; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_FILE_READ_DATA); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "try open for generic write\n"); + io.in.desired_access = SEC_GENERIC_WRITE; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_RIGHTS_FILE_WRITE); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "try open for generic read\n"); + io.in.desired_access = SEC_GENERIC_READ; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_RIGHTS_FILE_READ); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "set DACL with 0 aces\n"); + ZERO_STRUCT(dacl); + dacl.revision = SECURITY_ACL_REVISION_NT4; + dacl.num_aces = 0; + sd->dacl = &dacl; + + s.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + s.set_secdesc.in.file.handle = handle; + s.set_secdesc.in.secinfo_flags = SECINFO_DACL; + s.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &s); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "get the sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Testing the modified DACL */ + if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { + ret = false; + torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); + } + if (q.query_secdesc.out.sd->dacl == NULL) { + ret = false; + torture_fail_goto(tctx, done, "no DACL has been created on the server!\n"); + } + if (q.query_secdesc.out.sd->dacl->num_aces != 0) { + torture_result(tctx, TORTURE_FAIL, "DACL has %u aces!\n", + q.query_secdesc.out.sd->dacl->num_aces); + ret = false; + goto done; + } + + torture_comment(tctx, "try open for read control\n"); + io.in.desired_access = SEC_STD_READ_CONTROL; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_ACCESS_FLAGS(io.out.file.handle, + SEC_STD_READ_CONTROL); + smb2_util_close(tree, io.out.file.handle); + + torture_comment(tctx, "try open for write => access_denied\n"); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, tctx, &io); + if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + torture_comment(tctx, "try open for read => access_denied\n"); + io.in.desired_access = SEC_FILE_READ_DATA; + status = smb2_create(tree, tctx, &io); + if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + torture_comment(tctx, "try open for generic write => access_denied\n"); + io.in.desired_access = SEC_GENERIC_WRITE; + status = smb2_create(tree, tctx, &io); + if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + torture_comment(tctx, "try open for generic read => access_denied\n"); + io.in.desired_access = SEC_GENERIC_READ; + status = smb2_create(tree, tctx, &io); + if (torture_setting_bool(tctx, "hide_on_access_denied", false)) { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } else { + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + } + + torture_comment(tctx, "set empty sd\n"); + sd->type &= ~SEC_DESC_DACL_PRESENT; + sd->dacl = NULL; + + s.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + s.set_secdesc.in.file.handle = handle; + s.set_secdesc.in.secinfo_flags = SECINFO_DACL; + s.set_secdesc.in.sd = sd; + status = smb2_setinfo_file(tree, &s); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "get the sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Testing the modified DACL */ + if (!(q.query_secdesc.out.sd->type & SEC_DESC_DACL_PRESENT)) { + ret = false; + torture_fail_goto(tctx, done, "DACL_PRESENT flag not set by the server!\n"); + } + if (q.query_secdesc.out.sd->dacl != NULL) { + ret = false; + torture_fail_goto(tctx, done, "DACL has been created on the server!\n"); + } +done: + smb2_util_close(tree, handle); + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +/* + test SMB2 mkdir with OPEN_IF on the same name twice. + Must use 2 connections to hit the race. +*/ + +static bool test_mkdir_dup(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "mkdir_dup"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_tree **trees; + struct smb2_request **requests; + union smb_open *ios; + int i, num_files = 2; + int num_ok = 0; + int num_created = 0; + int num_existed = 0; + + torture_comment(tctx, + "Testing SMB2 Create Directory with multiple connections\n"); + trees = talloc_array(tctx, struct smb2_tree *, num_files); + requests = talloc_array(tctx, struct smb2_request *, num_files); + ios = talloc_array(tctx, union smb_open, num_files); + if ((tctx->ev == NULL) || (trees == NULL) || (requests == NULL) || + (ios == NULL)) { + torture_fail(tctx, ("talloc failed\n")); + ret = false; + goto done; + } + + tree->session->transport->options.request_timeout = 60; + + for (i=0; i<num_files; i++) { + if (!torture_smb2_connection(tctx, &(trees[i]))) { + torture_fail(tctx, + talloc_asprintf(tctx, + "Could not open %d'th connection\n", i)); + ret = false; + goto done; + } + trees[i]->session->transport->options.request_timeout = 60; + } + + /* cleanup */ + smb2_util_unlink(tree, fname); + smb2_util_rmdir(tree, fname); + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + io.smb2.in.create_flags = 0; + + for (i=0; i<num_files; i++) { + ios[i] = io; + requests[i] = smb2_create_send(trees[i], &(ios[i].smb2)); + if (requests[i] == NULL) { + torture_fail(tctx, + talloc_asprintf(tctx, + "could not send %d'th request\n", i)); + ret = false; + goto done; + } + } + + torture_comment(tctx, "waiting for replies\n"); + while (1) { + bool unreplied = false; + for (i=0; i<num_files; i++) { + if (requests[i] == NULL) { + continue; + } + if (requests[i]->state < SMB2_REQUEST_DONE) { + unreplied = true; + break; + } + status = smb2_create_recv(requests[i], tctx, + &(ios[i].smb2)); + + if (NT_STATUS_IS_OK(status)) { + num_ok += 1; + + if (ios[i].smb2.out.create_action == + NTCREATEX_ACTION_CREATED) { + num_created++; + } + if (ios[i].smb2.out.create_action == + NTCREATEX_ACTION_EXISTED) { + num_existed++; + } + } else { + torture_fail(tctx, + talloc_asprintf(tctx, + "File %d returned status %s\n", i, + nt_errstr(status))); + } + + + requests[i] = NULL; + } + if (!unreplied) { + break; + } + + if (tevent_loop_once(tctx->ev) != 0) { + torture_fail(tctx, "tevent_loop_once failed\n"); + ret = false; + goto done; + } + } + + if (num_ok != 2) { + torture_fail(tctx, + talloc_asprintf(tctx, + "num_ok == %d\n", num_ok)); + ret = false; + } + if (num_created != 1) { + torture_fail(tctx, + talloc_asprintf(tctx, + "num_created == %d\n", num_created)); + ret = false; + } + if (num_existed != 1) { + torture_fail(tctx, + talloc_asprintf(tctx, + "num_existed == %d\n", num_existed)); + ret = false; + } +done: + smb2_deltree(tree, fname); + + return ret; +} + +/* + test directory creation with an initial allocation size > 0 +*/ +static bool test_dir_alloc_size(struct torture_context *tctx, + struct smb2_tree *tree) +{ + bool ret = true; + const char *dname = DNAME "\\torture_alloc_size.dir"; + NTSTATUS status; + struct smb2_create c; + struct smb2_handle h1 = {{0}}, h2; + + torture_comment(tctx, "Checking initial allocation size on directories\n"); + + smb2_deltree(tree, dname); + + status = torture_smb2_testdir(tree, DNAME, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "torture_smb2_testdir failed"); + + ZERO_STRUCT(c); + c.in.create_disposition = NTCREATEX_DISP_CREATE; + c.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + c.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + c.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + c.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + c.in.fname = dname; + /* + * An insanely large value so we can check the value is + * ignored: Samba either returns 0 (current behaviour), or, + * once vfswrap_get_alloc_size() is fixed to allow retrieving + * the allocated size for directories, returns + * smb_roundup(..., stat.st_size) which would be 1 MB by + * default. + * + * Windows returns 0 for empty directories, once directories + * have a few entries it starts replying with values > 0. + */ + c.in.alloc_size = 1024*1024*1024; + + status = smb2_create(tree, tctx, &c); + h2 = c.out.file.handle; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "dir create with initial alloc size failed"); + + smb2_util_close(tree, h2); + + torture_comment(tctx, "Got directory alloc size: %ju\n", (uintmax_t)c.out.alloc_size); + + /* + * See above for the rational for this test + */ + if (c.out.alloc_size > 1024*1024) { + torture_fail_goto(tctx, done, talloc_asprintf(tctx, "bad alloc size: %ju", + (uintmax_t)c.out.alloc_size)); + } + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, DNAME); + return ret; +} + +static bool test_twrp_write(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + struct smb2_handle h1 = {{0}}; + NTSTATUS status; + bool ret = true; + char *p = NULL; + struct tm tm; + time_t t; + uint64_t nttime; + const char *file = NULL; + const char *snapshot = NULL; + + file = torture_setting_string(tctx, "twrp_file", NULL); + if (file == NULL) { + torture_skip(tctx, "missing 'twrp_file' option\n"); + } + + snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); + if (snapshot == NULL) { + torture_skip(tctx, "missing 'twrp_snapshot' option\n"); + } + + torture_comment(tctx, "Testing timewarp (%s) (%s)\n", file, snapshot); + + setenv("TZ", "GMT", 1); + + /* strptime does not set tm.tm_isdst but mktime assumes DST is in + * effect if it is greather than 1. */ + ZERO_STRUCT(tm); + + p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); + torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); + torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); + + t = mktime(&tm); + unix_to_nt_time(&nttime, t); + + io = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = file, + .in.query_maximal_access = true, + .in.timewarp = nttime, + }; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + smb2_util_close(tree, io.out.file.handle); + + ret = io.out.maximal_access & (SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA); + torture_assert_goto(tctx, ret, ret, done, "Bad access\n"); + + io = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = file, + .in.timewarp = nttime, + }; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + h1 = io.out.file.handle; + + status = smb2_util_write(tree, h1, "123", 0, 3); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_MEDIA_WRITE_PROTECTED, + ret, done, "smb2_create\n"); + + smb2_util_close(tree, h1); + +done: + return ret; +} + +static bool test_twrp_stream(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + bool ret = true; + char *p = NULL; + struct tm tm; + time_t t; + uint64_t nttime; + const char *file = NULL; + const char *stream = NULL; + const char *snapshot = NULL; + int stream_size; + char *path = NULL; + uint8_t *buf = NULL; + struct smb2_handle h1 = {{0}}; + struct smb2_read r; + + file = torture_setting_string(tctx, "twrp_file", NULL); + if (file == NULL) { + torture_skip(tctx, "missing 'twrp_file' option\n"); + } + + stream = torture_setting_string(tctx, "twrp_stream", NULL); + if (stream == NULL) { + torture_skip(tctx, "missing 'twrp_stream' option\n"); + } + + snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); + if (snapshot == NULL) { + torture_skip(tctx, "missing 'twrp_snapshot' option\n"); + } + + stream_size = torture_setting_int(tctx, "twrp_stream_size", 0); + if (stream_size == 0) { + torture_skip(tctx, "missing 'twrp_stream_size' option\n"); + } + + torture_comment(tctx, "Testing timewarp on stream (%s) (%s)\n", + file, snapshot); + + path = talloc_asprintf(tree, "%s:%s", file, stream); + torture_assert_not_null_goto(tctx, path, ret, done, "path\n"); + + buf = talloc_zero_array(tree, uint8_t, stream_size); + torture_assert_not_null_goto(tctx, buf, ret, done, "buf\n"); + + setenv("TZ", "GMT", 1); + + /* strptime does not set tm.tm_isdst but mktime assumes DST is in + * effect if it is greather than 1. */ + ZERO_STRUCT(tm); + + p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); + torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); + torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); + + t = mktime(&tm); + unix_to_nt_time(&nttime, t); + + io = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = path, + .in.timewarp = nttime, + }; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + h1 = io.out.file.handle; + + r = (struct smb2_read) { + .in.file.handle = h1, + .in.length = stream_size, + .in.offset = 0, + }; + + status = smb2_read(tree, tree, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + + smb2_util_close(tree, h1); + +done: + return ret; +} + +static bool test_twrp_openroot(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + bool ret = true; + char *p = NULL; + struct tm tm; + time_t t; + uint64_t nttime; + const char *snapshot = NULL; + + snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); + if (snapshot == NULL) { + torture_skip(tctx, "missing 'twrp_snapshot' option\n"); + } + + torture_comment(tctx, "Testing open of root of " + "share with timewarp (%s)\n", + snapshot); + + setenv("TZ", "GMT", 1); + + /* strptime does not set tm.tm_isdst but mktime assumes DST is in + * effect if it is greather than 1. */ + ZERO_STRUCT(tm); + + p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); + torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); + torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); + + t = mktime(&tm); + unix_to_nt_time(&nttime, t); + + io = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = "", + .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, + .in.timewarp = nttime, + }; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + smb2_util_close(tree, io.out.file.handle); + +done: + return ret; +} + +static bool test_twrp_listdir(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create create; + struct smb2_handle h = {{0}}; + struct smb2_find find; + unsigned int count; + union smb_search_data *d; + char *p = NULL; + struct tm tm; + time_t t; + uint64_t nttime; + const char *snapshot = NULL; + uint64_t normal_fileid; + uint64_t snapshot_fileid; + NTSTATUS status; + bool ret = true; + + snapshot = torture_setting_string(tctx, "twrp_snapshot", NULL); + if (snapshot == NULL) { + torture_fail(tctx, "missing 'twrp_snapshot' option\n"); + } + + torture_comment(tctx, "Testing File-Ids of directory listing " + "with timewarp (%s)\n", + snapshot); + + setenv("TZ", "GMT", 1); + + /* strptime does not set tm.tm_isdst but mktime assumes DST is in + * effect if it is greather than 1. */ + ZERO_STRUCT(tm); + + p = strptime(snapshot, "@GMT-%Y.%m.%d-%H.%M.%S", &tm); + torture_assert_goto(tctx, p != NULL, ret, done, "strptime\n"); + torture_assert_goto(tctx, *p == '\0', ret, done, "strptime\n"); + + t = mktime(&tm); + unix_to_nt_time(&nttime, t); + + /* + * 1: Query the file's File-Id + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = "subdir/hardlink", + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + smb2_util_close(tree, create.out.file.handle); + normal_fileid = BVAL(&create.out.on_disk_id, 0); + + /* + * 2: check directory listing of the file returns same File-Id + */ + + create = (struct smb2_create) { + .in.desired_access = SEC_DIR_LIST, + .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = "subdir", + .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + h = create.out.file.handle; + + find = (struct smb2_find) { + .in.file.handle = h, + .in.pattern = "*", + .in.max_response_size = 0x1000, + .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, + }; + + status = smb2_find_level(tree, tree, &find, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_find_level failed\n"); + + smb2_util_close(tree, h); + + torture_assert_int_equal_goto(tctx, count, 3, ret, done, "Bad count\n"); + torture_assert_str_equal_goto(tctx, + d[2].id_both_directory_info.name.s, + "hardlink", + ret, done, "bad name"); + torture_assert_u64_equal_goto(tctx, + d[2].id_both_directory_info.file_id, + normal_fileid, + ret, done, "bad fileid\n"); + + /* + * 3: Query File-Id of snapshot of the file and check the File-Id is + * different compared to the basefile + */ + + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = "subdir/hardlink", + .in.query_on_disk_id = true, + .in.timewarp = nttime, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + smb2_util_close(tree, create.out.file.handle); + + snapshot_fileid = BVAL(&create.out.on_disk_id, 0); + + /* + * 4: List directory of the snapshot and check the File-Id returned here + * is the same as in 3. + */ + + create = (struct smb2_create) { + .in.desired_access = SEC_DIR_LIST, + .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = "subdir", + .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, + .in.timewarp = nttime, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + h = create.out.file.handle; + + find = (struct smb2_find) { + .in.file.handle = h, + .in.pattern = "*", + .in.max_response_size = 0x1000, + .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, + }; + + status = smb2_find_level(tree, tree, &find, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_find_level failed\n"); + smb2_util_close(tree, h); + + torture_assert_int_equal_goto(tctx, count, 3, ret, done, "Bad count\n"); + torture_assert_str_equal_goto(tctx, + d[2].id_both_directory_info.name.s, + "hardlink", + ret, done, "bad name"); + torture_assert_u64_equal_goto(tctx, + snapshot_fileid, + d[2].id_both_directory_info.file_id, + ret, done, "bad fileid\n"); + +done: + return ret; +} + +static bool test_fileid(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *fname = DNAME "\\foo"; + const char *sname = DNAME "\\foo:bar"; + struct smb2_handle testdirh; + struct smb2_handle h1; + struct smb2_create create; + union smb_fileinfo finfo; + union smb_setfileinfo sinfo; + struct smb2_find f; + unsigned int count; + union smb_search_data *d; + uint64_t expected_fileid; + uint64_t returned_fileid; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &testdirh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + + /* + * Initial create with QFID + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.fname = fname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + expected_fileid = BVAL(&create.out.on_disk_id, 0); + + /* + * Getinfo the File-ID on the just opened handle + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Open existing with QFID + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = fname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the just opened handle + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Overwrite with QFID + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OVERWRITE, + .in.fname = fname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the open with overwrite handle + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Do some modifications on the basefile (IO, setinfo), verifying + * File-ID after each step. + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = fname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + + status = smb2_util_write(tree, h1, "foo", 0, strlen("foo")); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + sinfo = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = h1, + }; + unix_to_nt_time(&sinfo.basic_info.in.write_time, time(NULL)); + + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Create stream, check the stream's File-ID, should be the same as the + * base file (sic!, tested against Windows). + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the created stream + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Open stream, check the stream's File-ID, should be the same as the + * base file (sic!, tested against Windows). + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the opened stream + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Overwrite stream, check the stream's File-ID, should be the same as + * the base file (sic!, tested against Windows). + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OVERWRITE, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the overwritten stream + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Do some modifications on the stream (IO, setinfo), verifying File-ID + * after earch step. + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + + status = smb2_util_write(tree, h1, "foo", 0, strlen("foo")); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + sinfo = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = h1, + }; + unix_to_nt_time(&sinfo.basic_info.in.write_time, time(NULL)); + + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Final open of the basefile with QFID + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = fname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Final Getinfo checking File-ID + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Final list directory, verifying the operations on basefile and stream + * didn't modify the base file metadata. + */ + f = (struct smb2_find) { + .in.file.handle = testdirh, + .in.pattern = "foo", + .in.max_response_size = 0x1000, + .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, + .in.continue_flags = SMB2_CONTINUE_FLAG_RESTART, + }; + + status = smb2_find_level(tree, tree, &f, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_find_level failed\n"); + torture_assert_u64_equal_goto(tctx, + d->id_both_directory_info.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + +done: + smb2_util_close(tree, testdirh); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + return ret; +} + +static bool test_fileid_dir(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *dname = DNAME "\\foo"; + const char *sname = DNAME "\\foo:bar"; + struct smb2_handle testdirh; + struct smb2_handle h1; + struct smb2_create create; + union smb_fileinfo finfo; + union smb_setfileinfo sinfo; + struct smb2_find f; + unsigned int count; + union smb_search_data *d; + uint64_t expected_fileid; + uint64_t returned_fileid; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &testdirh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + + /* + * Initial directory create with QFID + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, + .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, + .in.fname = dname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + expected_fileid = BVAL(&create.out.on_disk_id, 0); + + /* + * Getinfo the File-ID on the just opened handle + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Open existing directory with QFID + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, + .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, + .in.fname = dname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the just opened handle + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Create stream, check the stream's File-ID, should be the same as the + * base file (sic!, tested against Windows). + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the created stream + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Open stream, check the stream's File-ID, should be the same as the + * base file (sic!, tested against Windows). + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the opened stream + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Overwrite stream, check the stream's File-ID, should be the same as + * the base file (sic!, tested against Windows). + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OVERWRITE, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Getinfo the File-ID on the overwritten stream + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Do some modifications on the stream (IO, setinfo), verifying File-ID + * after earch step. + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = sname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + + status = smb2_util_write(tree, h1, "foo", 0, strlen("foo")); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + sinfo = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = h1, + }; + unix_to_nt_time(&sinfo.basic_info.in.write_time, time(NULL)); + + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Final open of the directory with QFID + */ + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, + .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = dname, + .in.query_on_disk_id = true, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test file could not be created\n"); + h1 = create.out.file.handle; + returned_fileid = BVAL(&create.out.on_disk_id, 0); + torture_assert_u64_equal_goto(tctx, returned_fileid, expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Final Getinfo checking File-ID + */ + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + torture_assert_u64_equal_goto(tctx, finfo.all_info2.out.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + + /* + * Final list directory, verifying the operations on basefile and stream + * didn't modify the base file metadata. + */ + f = (struct smb2_find) { + .in.file.handle = testdirh, + .in.pattern = "foo", + .in.max_response_size = 0x1000, + .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, + .in.continue_flags = SMB2_CONTINUE_FLAG_RESTART, + }; + + status = smb2_find_level(tree, tree, &f, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_find_level failed\n"); + torture_assert_u64_equal_goto(tctx, + d->id_both_directory_info.file_id, + expected_fileid, + ret, done, "bad fileid\n"); + +done: + smb2_util_close(tree, testdirh); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + return ret; +} + +static bool test_fileid_unique_object( + struct torture_context *tctx, + struct smb2_tree *tree, + unsigned int num_objs, + bool create_dirs) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char *fname = NULL; + struct smb2_handle testdirh; + struct smb2_handle h1; + struct smb2_create create; + unsigned int i; + uint64_t fileid_array[num_objs]; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &testdirh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "test_fileid_unique failed\n"); + smb2_util_close(tree, testdirh); + + /* Create num_obj files as rapidly as we can. */ + for (i = 0; i < num_objs; i++) { + fname = talloc_asprintf(mem_ctx, + "%s\\testfile.%u", + DNAME, + i); + torture_assert_goto(tctx, + fname != NULL, + ret, + done, + "talloc failed\n"); + + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_ATTRIBUTE, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.fname = fname, + }; + + if (create_dirs) { + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.create_options = FILE_DIRECTORY_FILE; + } + + status = smb2_create(tree, tctx, &create); + if (!NT_STATUS_IS_OK(status)) { + torture_fail(tctx, + talloc_asprintf(tctx, + "test file %s could not be created\n", + fname)); + TALLOC_FREE(fname); + ret = false; + goto done; + } + + h1 = create.out.file.handle; + smb2_util_close(tree, h1); + TALLOC_FREE(fname); + } + + /* + * Get the file ids. + */ + for (i = 0; i < num_objs; i++) { + union smb_fileinfo finfo; + + fname = talloc_asprintf(mem_ctx, + "%s\\testfile.%u", + DNAME, + i); + torture_assert_goto(tctx, + fname != NULL, + ret, + done, + "talloc failed\n"); + + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_ATTRIBUTE, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.fname = fname, + }; + + if (create_dirs) { + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.create_options = FILE_DIRECTORY_FILE; + } + + status = smb2_create(tree, tctx, &create); + if (!NT_STATUS_IS_OK(status)) { + torture_fail(tctx, + talloc_asprintf(tctx, + "test file %s could not " + "be opened: %s\n", + fname, + nt_errstr(status))); + TALLOC_FREE(fname); + ret = false; + goto done; + } + + h1 = create.out.file.handle; + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + + status = smb2_getinfo_file(tree, tctx, &finfo); + if (!NT_STATUS_IS_OK(status)) { + torture_fail(tctx, + talloc_asprintf(tctx, + "failed to get fileid for " + "test file %s: %s\n", + fname, + nt_errstr(status))); + TALLOC_FREE(fname); + ret = false; + goto done; + } + smb2_util_close(tree, h1); + + fileid_array[i] = finfo.all_info2.out.file_id; + TALLOC_FREE(fname); + } + + /* All returned fileids must be unique. 100 is small so brute force. */ + for (i = 0; i < num_objs - 1; i++) { + unsigned int j; + for (j = i + 1; j < num_objs; j++) { + if (fileid_array[i] == fileid_array[j]) { + torture_fail(tctx, + talloc_asprintf(tctx, + "fileid %u == fileid %u (0x%"PRIu64")\n", + i, + j, + fileid_array[i])); + ret = false; + goto done; + } + } + } + +done: + + smb2_util_close(tree, testdirh); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + return ret; +} + +static bool test_fileid_unique( + struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_fileid_unique_object(tctx, tree, 100, false); +} + +static bool test_fileid_unique_dir( + struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_fileid_unique_object(tctx, tree, 100, true); +} + +static bool test_dosattr_tmp_dir(struct torture_context *tctx, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_create c; + struct smb2_handle h1 = {{0}}; + const char *fname = DNAME; + + smb2_deltree(tree, fname); + smb2_util_rmdir(tree, fname); + + c = (struct smb2_create) { + .in.desired_access = SEC_RIGHTS_DIR_ALL, + .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE, + .in.create_options = NTCREATEX_OPTIONS_DIRECTORY, + .in.fname = DNAME, + }; + + status = smb2_create(tree, tctx, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create\n"); + h1 = c.out.file.handle; + + /* Try to set temporary attribute on directory */ + SET_ATTRIB(FILE_ATTRIBUTE_TEMPORARY); + + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_INVALID_PARAMETER, + ret, done, + "Unexpected setinfo result\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_util_unlink(tree, fname); + smb2_deltree(tree, fname); + + return ret; +} + +/* + test opening quota fakefile handle and returned attributes +*/ +static bool test_smb2_open_quota_fake_file(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "$Extend\\$Quota:$Q:$INDEX_ALLOCATION"; + struct smb2_create create; + struct smb2_handle h = {{0}}; + NTSTATUS status; + bool ret = true; + + create = (struct smb2_create) { + .in.desired_access = SEC_RIGHTS_FILE_READ, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tree, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h = create.out.file.handle; + + torture_assert_u64_equal_goto(tctx, + create.out.file_attr, + FILE_ATTRIBUTE_HIDDEN + | FILE_ATTRIBUTE_SYSTEM + | FILE_ATTRIBUTE_DIRECTORY + | FILE_ATTRIBUTE_ARCHIVE, + ret, + done, + "Wrong attributes\n"); + + torture_assert_u64_equal_goto(tctx, + create.out.create_time, 0, + ret, + done, + "create_time is not 0\n"); + torture_assert_u64_equal_goto(tctx, + create.out.access_time, 0, + ret, + done, + "access_time is not 0\n"); + torture_assert_u64_equal_goto(tctx, + create.out.write_time, 0, + ret, + done, + "write_time is not 0\n"); + torture_assert_u64_equal_goto(tctx, + create.out.change_time, 0, + ret, + done, + "change_time is not 0\n"); + +done: + smb2_util_close(tree, h); + return ret; +} + +/* + stress testing path base operations + e.g. contention on lockting.tdb records + */ + +struct test_smb2_bench_path_contention_shared_conn; +struct test_smb2_bench_path_contention_shared_loop; + +struct test_smb2_bench_path_contention_shared_state { + struct torture_context *tctx; + size_t num_conns; + struct test_smb2_bench_path_contention_shared_conn *conns; + size_t num_loops; + struct test_smb2_bench_path_contention_shared_loop *loops; + struct timeval starttime; + int timecount; + int timelimit; + struct { + uint64_t num_finished; + double total_latency; + double min_latency; + double max_latency; + } opens; + struct { + uint64_t num_finished; + double total_latency; + double min_latency; + double max_latency; + } closes; + bool ok; + bool stop; +}; + +struct test_smb2_bench_path_contention_shared_conn { + struct test_smb2_bench_path_contention_shared_state *state; + int idx; + struct smb2_tree *tree; +}; + +struct test_smb2_bench_path_contention_shared_loop { + struct test_smb2_bench_path_contention_shared_state *state; + struct test_smb2_bench_path_contention_shared_conn *conn; + int idx; + struct tevent_immediate *im; + struct { + struct smb2_create io; + struct smb2_request *req; + struct timeval starttime; + uint64_t num_started; + uint64_t num_finished; + double total_latency; + double min_latency; + double max_latency; + } opens; + struct { + struct smb2_close io; + struct smb2_request *req; + struct timeval starttime; + uint64_t num_started; + uint64_t num_finished; + double total_latency; + double min_latency; + double max_latency; + } closes; + NTSTATUS error; +}; + +static void test_smb2_bench_path_contention_loop_open( + struct test_smb2_bench_path_contention_shared_loop *loop); + +static void test_smb2_bench_path_contention_loop_start(struct tevent_context *ctx, + struct tevent_immediate *im, + void *private_data) +{ + struct test_smb2_bench_path_contention_shared_loop *loop = + (struct test_smb2_bench_path_contention_shared_loop *) + private_data; + + test_smb2_bench_path_contention_loop_open(loop); +} + +static void test_smb2_bench_path_contention_loop_opened(struct smb2_request *req); + +static void test_smb2_bench_path_contention_loop_open( + struct test_smb2_bench_path_contention_shared_loop *loop) +{ + struct test_smb2_bench_path_contention_shared_state *state = loop->state; + + loop->opens.num_started += 1; + loop->opens.starttime = timeval_current(); + loop->opens.req = smb2_create_send(loop->conn->tree, &loop->opens.io); + torture_assert_goto(state->tctx, loop->opens.req != NULL, + state->ok, asserted, "smb2_create_send"); + + loop->opens.req->async.fn = test_smb2_bench_path_contention_loop_opened; + loop->opens.req->async.private_data = loop; + return; +asserted: + state->stop = true; +} + +static void test_smb2_bench_path_contention_loop_close( + struct test_smb2_bench_path_contention_shared_loop *loop); + +static void test_smb2_bench_path_contention_loop_opened(struct smb2_request *req) +{ + struct test_smb2_bench_path_contention_shared_loop *loop = + (struct test_smb2_bench_path_contention_shared_loop *) + req->async.private_data; + struct test_smb2_bench_path_contention_shared_state *state = loop->state; + double latency = timeval_elapsed(&loop->opens.starttime); + TALLOC_CTX *frame = talloc_stackframe(); + + torture_assert_goto(state->tctx, loop->opens.req == req, + state->ok, asserted, __location__); + loop->error = smb2_create_recv(req, frame, &loop->opens.io); + torture_assert_ntstatus_ok_goto(state->tctx, loop->error, + state->ok, asserted, __location__); + ZERO_STRUCT(loop->opens.io.out.blobs); + SMB_ASSERT(latency >= 0.000001); + + if (loop->opens.num_finished == 0) { + /* first round */ + loop->opens.min_latency = latency; + loop->opens.max_latency = latency; + } + + loop->opens.num_finished += 1; + loop->opens.total_latency += latency; + + if (latency < loop->opens.min_latency) { + loop->opens.min_latency = latency; + } + + if (latency > loop->opens.max_latency) { + loop->opens.max_latency = latency; + } + + TALLOC_FREE(frame); + test_smb2_bench_path_contention_loop_close(loop); + return; +asserted: + state->stop = true; + TALLOC_FREE(frame); +} + +static void test_smb2_bench_path_contention_loop_closed(struct smb2_request *req); + +static void test_smb2_bench_path_contention_loop_close( + struct test_smb2_bench_path_contention_shared_loop *loop) +{ + struct test_smb2_bench_path_contention_shared_state *state = loop->state; + + loop->closes.num_started += 1; + loop->closes.starttime = timeval_current(); + loop->closes.io.in.file = loop->opens.io.out.file; + loop->closes.req = smb2_close_send(loop->conn->tree, &loop->closes.io); + torture_assert_goto(state->tctx, loop->closes.req != NULL, + state->ok, asserted, "smb2_close_send"); + + loop->closes.req->async.fn = test_smb2_bench_path_contention_loop_closed; + loop->closes.req->async.private_data = loop; + return; +asserted: + state->stop = true; +} + +static void test_smb2_bench_path_contention_loop_closed(struct smb2_request *req) +{ + struct test_smb2_bench_path_contention_shared_loop *loop = + (struct test_smb2_bench_path_contention_shared_loop *) + req->async.private_data; + struct test_smb2_bench_path_contention_shared_state *state = loop->state; + double latency = timeval_elapsed(&loop->closes.starttime); + + torture_assert_goto(state->tctx, loop->closes.req == req, + state->ok, asserted, __location__); + loop->error = smb2_close_recv(req, &loop->closes.io); + torture_assert_ntstatus_ok_goto(state->tctx, loop->error, + state->ok, asserted, __location__); + SMB_ASSERT(latency >= 0.000001); + if (loop->closes.num_finished == 0) { + /* first round */ + loop->closes.min_latency = latency; + loop->closes.max_latency = latency; + } + loop->closes.num_finished += 1; + + loop->closes.total_latency += latency; + + if (latency < loop->closes.min_latency) { + loop->closes.min_latency = latency; + } + + if (latency > loop->closes.max_latency) { + loop->closes.max_latency = latency; + } + + test_smb2_bench_path_contention_loop_open(loop); + return; +asserted: + state->stop = true; +} + +static void test_smb2_bench_path_contention_progress(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct test_smb2_bench_path_contention_shared_state *state = + (struct test_smb2_bench_path_contention_shared_state *)private_data; + uint64_t num_opens = 0; + double total_open_latency = 0; + double min_open_latency = 0; + double max_open_latency = 0; + double avs_open_latency = 0; + uint64_t num_closes = 0; + double total_close_latency = 0; + double min_close_latency = 0; + double max_close_latency = 0; + double avs_close_latency = 0; + size_t i; + + state->timecount += 1; + + for (i=0;i<state->num_loops;i++) { + struct test_smb2_bench_path_contention_shared_loop *loop = + &state->loops[i]; + + num_opens += loop->opens.num_finished; + total_open_latency += loop->opens.total_latency; + if (min_open_latency == 0.0 && loop->opens.min_latency != 0.0) { + min_open_latency = loop->opens.min_latency; + } + if (loop->opens.min_latency < min_open_latency) { + min_open_latency = loop->opens.min_latency; + } + if (max_open_latency == 0.0) { + max_open_latency = loop->opens.max_latency; + } + if (loop->opens.max_latency > max_open_latency) { + max_open_latency = loop->opens.max_latency; + } + loop->opens.num_finished = 0; + loop->opens.total_latency = 0.0; + + num_closes += loop->closes.num_finished; + total_close_latency += loop->closes.total_latency; + if (min_close_latency == 0.0 && loop->closes.min_latency != 0.0) { + min_close_latency = loop->closes.min_latency; + } + if (loop->closes.min_latency < min_close_latency) { + min_close_latency = loop->closes.min_latency; + } + if (max_close_latency == 0.0) { + max_close_latency = loop->closes.max_latency; + } + if (loop->closes.max_latency > max_close_latency) { + max_close_latency = loop->closes.max_latency; + } + loop->closes.num_finished = 0; + loop->closes.total_latency = 0.0; + } + + state->opens.num_finished += num_opens; + state->opens.total_latency += total_open_latency; + if (state->opens.min_latency == 0.0 && min_open_latency != 0.0) { + state->opens.min_latency = min_open_latency; + } + if (min_open_latency < state->opens.min_latency) { + state->opens.min_latency = min_open_latency; + } + if (state->opens.max_latency == 0.0) { + state->opens.max_latency = max_open_latency; + } + if (max_open_latency > state->opens.max_latency) { + state->opens.max_latency = max_open_latency; + } + + state->closes.num_finished += num_closes; + state->closes.total_latency += total_close_latency; + if (state->closes.min_latency == 0.0 && min_close_latency != 0.0) { + state->closes.min_latency = min_close_latency; + } + if (min_close_latency < state->closes.min_latency) { + state->closes.min_latency = min_close_latency; + } + if (state->closes.max_latency == 0.0) { + state->closes.max_latency = max_close_latency; + } + if (max_close_latency > state->closes.max_latency) { + state->closes.max_latency = max_close_latency; + } + + if (state->timecount < state->timelimit) { + te = tevent_add_timer(state->tctx->ev, + state, + timeval_current_ofs(1, 0), + test_smb2_bench_path_contention_progress, + state); + torture_assert_goto(state->tctx, te != NULL, + state->ok, asserted, "tevent_add_timer"); + + if (!torture_setting_bool(state->tctx, "progress", true)) { + return; + } + + avs_open_latency = total_open_latency / num_opens; + avs_close_latency = total_close_latency / num_closes; + + torture_comment(state->tctx, + "%.2f second: " + "open[num/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f] " + "close[num/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f] \r", + timeval_elapsed(&state->starttime), + (unsigned long long)num_opens, + avs_open_latency, + min_open_latency, + max_open_latency, + (unsigned long long)num_closes, + avs_close_latency, + min_close_latency, + max_close_latency); + return; + } + + avs_open_latency = state->opens.total_latency / state->opens.num_finished; + avs_close_latency = state->closes.total_latency / state->closes.num_finished; + num_opens = state->opens.num_finished / state->timelimit; + num_closes = state->closes.num_finished / state->timelimit; + + torture_comment(state->tctx, + "%.2f second: " + "open[num/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f] " + "close[num/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f]\n", + timeval_elapsed(&state->starttime), + (unsigned long long)num_opens, + avs_open_latency, + state->opens.min_latency, + state->opens.max_latency, + (unsigned long long)num_closes, + avs_close_latency, + state->closes.min_latency, + state->closes.max_latency); + +asserted: + state->stop = true; +} + +static bool test_smb2_bench_path_contention_shared(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct test_smb2_bench_path_contention_shared_state *state = NULL; + bool ret = true; + int torture_nprocs = torture_setting_int(tctx, "nprocs", 4); + int torture_qdepth = torture_setting_int(tctx, "qdepth", 1); + size_t i; + size_t li = 0; + int timelimit = torture_setting_int(tctx, "timelimit", 10); + const char *path = torture_setting_string(tctx, "bench_path", ""); + struct smb2_create open_io = { .level = RAW_OPEN_SMB2, }; + struct smb2_close close_io = { .level = RAW_CLOSE_SMB2, }; + struct tevent_timer *te = NULL; + uint32_t timeout_msec; + + state = talloc_zero(tctx, struct test_smb2_bench_path_contention_shared_state); + torture_assert(tctx, state != NULL, __location__); + state->tctx = tctx; + state->num_conns = torture_nprocs; + state->conns = talloc_zero_array(state, + struct test_smb2_bench_path_contention_shared_conn, + state->num_conns); + torture_assert(tctx, state->conns != NULL, __location__); + state->num_loops = torture_nprocs * torture_qdepth; + state->loops = talloc_zero_array(state, + struct test_smb2_bench_path_contention_shared_loop, + state->num_loops); + torture_assert(tctx, state->loops != NULL, __location__); + state->ok = true; + state->timelimit = MAX(timelimit, 1); + + open_io.in.desired_access = SEC_DIR_READ_ATTRIBUTE; + open_io.in.alloc_size = 0; + open_io.in.file_attributes = 0; + open_io.in.share_access = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; + open_io.in.create_disposition = FILE_OPEN; + open_io.in.create_options = FILE_OPEN_REPARSE_POINT; + open_io.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + open_io.in.security_flags = 0; + open_io.in.fname = path; + open_io.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + open_io.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + torture_comment(tctx, "Opening %zd connections\n", state->num_conns); + + for (i=0;i<state->num_conns;i++) { + struct smb2_tree *ct = NULL; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + size_t pcli; + + state->conns[i].state = state; + state->conns[i].idx = i; + + if (!torture_smb2_connection(tctx, &ct)) { + torture_comment(tctx, "Failed opening %zd/%zd connections\n", i, state->num_conns); + return false; + } + state->conns[i].tree = talloc_steal(state->conns, ct); + + smb2cli_conn_set_max_credits(ct->session->transport->conn, 8192); + smb2cli_ioctl(ct->session->transport->conn, + timeout_msec, + ct->session->smbXcli, + ct->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + UINT32_MAX, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + ct, + &out_input_buffer, + &out_output_buffer); + torture_assert(tctx, + smbXcli_conn_is_connected(ct->session->transport->conn), + "smbXcli_conn_is_connected"); + for (pcli = 0; pcli < torture_qdepth; pcli++) { + struct test_smb2_bench_path_contention_shared_loop *loop = &state->loops[li]; + + loop->idx = li++; + loop->state = state; + loop->conn = &state->conns[i]; + loop->im = tevent_create_immediate(state->loops); + torture_assert(tctx, loop->im != NULL, __location__); + loop->opens.io = open_io; + loop->closes.io = close_io; + + tevent_schedule_immediate(loop->im, + tctx->ev, + test_smb2_bench_path_contention_loop_start, + loop); + } + } + + torture_comment(tctx, "Opened %zu connections with qdepth=%d => %zu loops\n", + state->num_conns, torture_qdepth, state->num_loops); + + torture_comment(tctx, "Running for %d seconds\n", state->timelimit); + + state->starttime = timeval_current(); + + te = tevent_add_timer(tctx->ev, + state, + timeval_current_ofs(1, 0), + test_smb2_bench_path_contention_progress, + state); + torture_assert(tctx, te != NULL, __location__); + + while (!state->stop) { + int rc = tevent_loop_once(tctx->ev); + torture_assert_int_equal(tctx, rc, 0, "tevent_loop_once"); + } + + torture_comment(tctx, "%.2f seconds\n", timeval_elapsed(&state->starttime)); + TALLOC_FREE(state); + return ret; +} + +/* + basic testing of SMB2 read +*/ +struct torture_suite *torture_smb2_create_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "create"); + + torture_suite_add_1smb2_test(suite, "gentest", test_create_gentest); + torture_suite_add_1smb2_test(suite, "blob", test_create_blob); + torture_suite_add_1smb2_test(suite, "open", test_smb2_open); + torture_suite_add_1smb2_test(suite, "brlocked", test_smb2_open_brlocked); + torture_suite_add_1smb2_test(suite, "multi", test_smb2_open_multi); + torture_suite_add_1smb2_test(suite, "delete", test_smb2_open_for_delete); + torture_suite_add_1smb2_test(suite, "leading-slash", test_smb2_leading_slash); + torture_suite_add_1smb2_test(suite, "impersonation", test_smb2_impersonation_level); + torture_suite_add_1smb2_test(suite, "aclfile", test_create_acl_file); + torture_suite_add_1smb2_test(suite, "acldir", test_create_acl_dir); + torture_suite_add_1smb2_test(suite, "nulldacl", test_create_null_dacl); + torture_suite_add_1smb2_test(suite, "mkdir-dup", test_mkdir_dup); + torture_suite_add_1smb2_test(suite, "dir-alloc-size", test_dir_alloc_size); + torture_suite_add_1smb2_test(suite, "dosattr_tmp_dir", test_dosattr_tmp_dir); + torture_suite_add_1smb2_test(suite, "quota-fake-file", test_smb2_open_quota_fake_file); + + torture_suite_add_1smb2_test(suite, "bench-path-contention-shared", test_smb2_bench_path_contention_shared); + + suite->description = talloc_strdup(suite, "SMB2-CREATE tests"); + + return suite; +} + +struct torture_suite *torture_smb2_twrp_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "twrp"); + + torture_suite_add_1smb2_test(suite, "write", test_twrp_write); + torture_suite_add_1smb2_test(suite, "stream", test_twrp_stream); + torture_suite_add_1smb2_test(suite, "openroot", test_twrp_openroot); + torture_suite_add_1smb2_test(suite, "listdir", test_twrp_listdir); + + suite->description = talloc_strdup(suite, "SMB2-TWRP tests"); + + return suite; +} + +/* + basic testing of SMB2 File-IDs +*/ +struct torture_suite *torture_smb2_fileid_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "fileid"); + + torture_suite_add_1smb2_test(suite, "fileid", test_fileid); + torture_suite_add_1smb2_test(suite, "fileid-dir", test_fileid_dir); + torture_suite_add_1smb2_test(suite, "unique", test_fileid_unique); + torture_suite_add_1smb2_test(suite, "unique-dir", test_fileid_unique_dir); + + suite->description = talloc_strdup(suite, "SMB2-FILEID tests"); + + return suite; +} + +struct torture_suite *torture_smb2_bench_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "bench"); + + torture_suite_add_1smb2_test(suite, "oplock1", test_smb2_bench_oplock); + torture_suite_add_1smb2_test(suite, "path-contention-shared", test_smb2_bench_path_contention_shared); + torture_suite_add_1smb2_test(suite, "echo", test_smb2_bench_echo); + + suite->description = talloc_strdup(suite, "SMB2-BENCH tests"); + + return suite; +} + +static bool test_no_stream(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create c; + NTSTATUS status; + bool ret = true; + const char *names[] = { + "test_no_stream::$DATA", + "test_no_stream::foooooooooooo", + "test_no_stream:stream", + "test_no_stream:stream:$DATA", + NULL + }; + int i; + + for (i = 0; names[i] != NULL; i++) { + c = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = names[i], + }; + + status = smb2_create(tree, tctx, &c); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) { + torture_comment( + tctx, "Expected NT_STATUS_OBJECT_NAME_INVALID, " + "got %s, name: '%s'\n", + nt_errstr(status), names[i]); + torture_fail_goto(tctx, done, "Bad create result\n"); + } + } +done: + return ret; +} + +struct torture_suite *torture_smb2_create_no_streams_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "create_no_streams"); + + torture_suite_add_1smb2_test(suite, "no_stream", test_no_stream); + + suite->description = talloc_strdup(suite, "SMB2-CREATE stream test on share without streams support"); + + return suite; +} diff --git a/source4/torture/smb2/credits.c b/source4/torture/smb2/credits.c new file mode 100644 index 0000000..b06bae7 --- /dev/null +++ b/source4/torture/smb2/credits.c @@ -0,0 +1,268 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 credits + + Copyright (C) Ralph Boehme 2017 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" +#include "lib/param/param.h" + +/** + * Request 64k credits in negprot/sessionsetup and require at least 8k + * + * This passes against Windows 2016 + **/ +static bool test_session_setup_credits_granted(struct torture_context *tctx, + struct smb2_tree *_tree) +{ + struct smbcli_options options; + struct smb2_transport *transport = NULL; + struct smb2_tree *tree = NULL; + uint16_t cur_credits; + NTSTATUS status; + bool ret = true; + + transport = _tree->session->transport; + options = transport->options; + + status = smb2_logoff(_tree->session); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_logoff failed\n"); + TALLOC_FREE(_tree); + + options.max_credits = 65535; + + ret = torture_smb2_connection_ext(tctx, 0, &options, &tree); + torture_assert_goto(tctx, ret == true, ret, done, + "torture_smb2_connection_ext failed\n"); + + transport = tree->session->transport; + + cur_credits = smb2cli_conn_get_cur_credits(transport->conn); + if (cur_credits < 8192) { + torture_result(tctx, TORTURE_FAIL, + "Server only granted %" PRIu16" credits\n", + cur_credits); + ret = false; + goto done; + } + +done: + TALLOC_FREE(tree); + return ret; +} + +/** + * Request 64K credits in a single SMB2 request and requite at least 8192 + * + * This passes against Windows 2016 + **/ +static bool test_single_req_credits_granted(struct torture_context *tctx, + struct smb2_tree *_tree) +{ + struct smbcli_options options; + struct smb2_transport *transport = NULL; + struct smb2_tree *tree = NULL; + struct smb2_handle h = {{0}}; + struct smb2_create create; + const char *fname = "single_req_credits_granted.dat"; + uint16_t cur_credits; + NTSTATUS status; + bool ret = true; + + smb2_util_unlink(_tree, fname); + + transport = _tree->session->transport; + options = transport->options; + + status = smb2_logoff(_tree->session); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_logoff failed\n"); + TALLOC_FREE(_tree); + + options.max_credits = 1; + + ret = torture_smb2_connection_ext(tctx, 0, &options, &tree); + torture_assert_goto(tctx, ret == true, ret, done, + "torture_smb2_connection_ext failed\n"); + + transport = tree->session->transport; + + cur_credits = smb2cli_conn_get_cur_credits(transport->conn); + if (cur_credits != 1) { + torture_result(tctx, TORTURE_FAIL, + "Only wanted 1 credit but server granted %" PRIu16"\n", + cur_credits); + ret = false; + goto done; + } + + smb2cli_conn_set_max_credits(transport->conn, 65535); + + ZERO_STRUCT(create); + create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.fname = fname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h = create.out.file.handle; + + cur_credits = smb2cli_conn_get_cur_credits(transport->conn); + if (cur_credits < 8192) { + torture_result(tctx, TORTURE_FAIL, + "Server only granted %" PRIu16" credits\n", + cur_credits); + ret = false; + goto done; + } + +done: + if (!smb2_util_handle_empty(h)) { + smb2_util_close(tree, h); + } + smb2_util_unlink(tree, fname); + TALLOC_FREE(tree); + return ret; +} + +static bool test_crediting_skipped_mid(struct torture_context *tctx, + struct smb2_tree *_tree) +{ + struct smbcli_options options; + struct smb2_transport *transport = NULL; + struct smb2_tree *tree = NULL; + struct smb2_handle h = {{0}}; + struct smb2_create create; + const char *fname = "skipped_mid.dat"; + uint64_t mid; + uint16_t cur_credits; + NTSTATUS status; + bool ret = true; + int i; + + smb2_util_unlink(_tree, fname); + + transport = _tree->session->transport; + options = transport->options; + + status = smb2_logoff(_tree->session); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_logoff failed\n"); + TALLOC_FREE(_tree); + + options.max_credits = 8192; + + ret = torture_smb2_connection_ext(tctx, 0, &options, &tree); + torture_assert_goto(tctx, ret == true, ret, done, "torture_smb2_connection_ext failed\n"); + + transport = tree->session->transport; + + cur_credits = smb2cli_conn_get_cur_credits(transport->conn); + if (cur_credits != 8192) { + torture_result(tctx, TORTURE_FAIL, "Server only granted %" PRIu16" credits\n", cur_credits); + ret = false; + goto done; + } + + ZERO_STRUCT(create); + create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.fname = fname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + h = create.out.file.handle; + + /* + * See what happens if we skip a mid. As we want to avoid triggering our + * client side mid window check we keep conn->smb2.cur_credits + * unchanged so the server keeps granting credits until it's max mid + * windows size is reached at which point it will disconnect us: + * + * o Windows 2016 currently has a maximum mid window size of 8192 by + * default + * + * o Samba's limit is 512 + * + * o Windows 2008r2 uses some special algorithm (MS-SMB2 3.3.1.1 + * footnote <167>) that kicks in once a mid is skipped, resulting in a + * maximum window size of 100-300 depending on the number of granted + * credits at the moment of skipping a mid. + */ + + mid = smb2cli_conn_get_mid(tree->session->transport->conn); + smb2cli_conn_set_mid(tree->session->transport->conn, mid + 1); + + for (i = 0; i < 8191; i++) { + status = smb2_util_write(tree, h, "\0", 0, 1); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "Server only allowed %d writes\n", i); + ret = false; + goto done; + } + } + + /* + * Now use the skipped mid (the smb2_util_close...), we should + * immediately get a full mid window of size 8192. + */ + smb2cli_conn_set_mid(tree->session->transport->conn, mid); + status = smb2_util_close(tree, h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_close failed\n"); + ZERO_STRUCT(h); + + cur_credits = smb2cli_conn_get_cur_credits(transport->conn); + if (cur_credits != 8192) { + torture_result(tctx, TORTURE_FAIL, "Server only granted %" PRIu16" credits\n", cur_credits); + ret = false; + goto done; + } + + smb2cli_conn_set_mid(tree->session->transport->conn, mid + 8192); + +done: + if (!smb2_util_handle_empty(h)) { + smb2_util_close(tree, h); + } + smb2_util_unlink(tree, fname); + TALLOC_FREE(tree); + return ret; +} + +struct torture_suite *torture_smb2_crediting_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "credits"); + + torture_suite_add_1smb2_test(suite, "session_setup_credits_granted", test_session_setup_credits_granted); + torture_suite_add_1smb2_test(suite, "single_req_credits_granted", test_single_req_credits_granted); + torture_suite_add_1smb2_test(suite, "skipped_mid", test_crediting_skipped_mid); + + suite->description = talloc_strdup(suite, "SMB2-CREDITS tests"); + + return suite; +} diff --git a/source4/torture/smb2/delete-on-close.c b/source4/torture/smb2/delete-on-close.c new file mode 100644 index 0000000..0524287 --- /dev/null +++ b/source4/torture/smb2/delete-on-close.c @@ -0,0 +1,762 @@ +/* + Unix SMB/CIFS implementation. + + test delete-on-close in more detail + + Copyright (C) Richard Sharpe, 2013 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" + +#define DNAME "test_dir" +#define FNAME DNAME "\\test_create.dat" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + return false; \ + }} while (0) + +static bool create_dir(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + struct smb2_handle handle; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig; + const char *owner_sid; + uint32_t perms = 0; + + torture_comment(tctx, "Creating Directory for testing: %s\n", DNAME); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = DNAME; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + /* + * We create an SD that allows us to do most things but we do not + * get DELETE and DELETE CHILD access! + */ + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_OWNER | + SEC_STD_WRITE_DAC | SEC_STD_READ_CONTROL | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_TRAVERSE | SEC_DIR_WRITE_EA | + SEC_FILE_READ_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA | SEC_FILE_READ_DATA; + + torture_comment(tctx, "Setting permissions on dir to 0x1e01bf\n"); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + perms, + SEC_ACE_FLAG_OBJECT_INHERIT, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, handle); + + return true; +} + +static bool set_dir_delete_perms(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create io; + struct smb2_handle handle; + union smb_fileinfo q; + union smb_setfileinfo set; + struct security_descriptor *sd, *sd_orig; + const char *owner_sid; + uint32_t perms = 0; + + torture_comment(tctx, "Opening Directory for setting new SD: %s\n", DNAME); + + ZERO_STRUCT(io); + io.level = RAW_OPEN_SMB2; + io.in.create_flags = 0; + io.in.desired_access = + SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.alloc_size = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.security_flags = 0; + io.in.fname = DNAME; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + handle = io.out.file.handle; + + torture_comment(tctx, "get the original sd\n"); + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + /* + * We create an SD that allows us to do most things including + * get DELETE and DELETE CHILD access! + */ + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_OWNER | + SEC_STD_WRITE_DAC | SEC_STD_READ_CONTROL | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_TRAVERSE | SEC_DIR_WRITE_EA | + SEC_FILE_READ_EA | SEC_FILE_APPEND_DATA | + SEC_DIR_DELETE_CHILD | SEC_STD_DELETE | + SEC_FILE_WRITE_DATA | SEC_FILE_READ_DATA; + + torture_comment(tctx, "Setting permissions on dir to 0x%0x\n", perms); + sd = security_descriptor_dacl_create(tctx, + 0, owner_sid, NULL, + owner_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, + perms, + 0, + NULL); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = handle; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, handle); + + return true; +} + +static bool test_doc_overwrite_if(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + uint32_t perms = 0; + + /* File should not exist for this first test, so make sure */ + set_dir_delete_perms(tctx, tree); + + smb2_deltree(tree, DNAME); + + create_dir(tctx, tree); + + torture_comment(tctx, "Create file with DeleteOnClose on non-existent file (OVERWRITE_IF)\n"); + torture_comment(tctx, "We expect NT_STATUS_OK\n"); + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA; + + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + + /* Check it was deleted */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = 0; + io.in.fname = FNAME; + + torture_comment(tctx, "Testing if the file was deleted when closed\n"); + torture_comment(tctx, "We expect NT_STATUS_OBJECT_NAME_NOT_FOUND\n"); + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + return true; +} + +static bool test_doc_overwrite_if_exist(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + uint32_t perms = 0; + + /* File should not exist for this first test, so make sure */ + /* And set the SEC Descriptor appropriately */ + set_dir_delete_perms(tctx, tree); + + smb2_deltree(tree, DNAME); + + create_dir(tctx, tree); + + torture_comment(tctx, "Create file with DeleteOnClose on existing file (OVERWRITE_IF)\n"); + torture_comment(tctx, "We expect NT_STATUS_ACCESS_DENIED\n"); + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA; + + /* First, create this file ... */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = 0x0; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + + /* Next, try to open it for Delete On Close */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + status = smb2_util_close(tree, io.out.file.handle); + + return true; +} + +static bool test_doc_create(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + uint32_t perms = 0; + + /* File should not exist for this first test, so make sure */ + set_dir_delete_perms(tctx, tree); + + smb2_deltree(tree, DNAME); + + create_dir(tctx, tree); + + torture_comment(tctx, "Create file with DeleteOnClose on non-existent file (CREATE) \n"); + torture_comment(tctx, "We expect NT_STATUS_OK\n"); + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA; + + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + + /* Check it was deleted */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = 0; + io.in.fname = FNAME; + + torture_comment(tctx, "Testing if the file was deleted when closed\n"); + torture_comment(tctx, "We expect NT_STATUS_OBJECT_NAME_NOT_FOUND\n"); + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + return true; +} + +static bool test_doc_create_exist(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + uint32_t perms = 0; + + /* File should not exist for this first test, so make sure */ + set_dir_delete_perms(tctx, tree); + + smb2_deltree(tree, DNAME); + + create_dir(tctx, tree); + + torture_comment(tctx, "Create file with DeleteOnClose on non-existent file (CREATE) \n"); + torture_comment(tctx, "We expect NT_STATUS_OBJECT_NAME_COLLISION\n"); + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA; + + /* First, create the file */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = 0x0; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + + /* Next, try to open it for Delete on Close */ + status = smb2_util_close(tree, io.out.file.handle); + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + + status = smb2_util_close(tree, io.out.file.handle); + + return true; +} + +static bool test_doc_create_if(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + uint32_t perms = 0; + + /* File should not exist for this first test, so make sure */ + set_dir_delete_perms(tctx, tree); + + smb2_deltree(tree, DNAME); + + create_dir(tctx, tree); + + torture_comment(tctx, "Create file with DeleteOnClose on non-existent file (OPEN_IF)\n"); + torture_comment(tctx, "We expect NT_STATUS_OK\n"); + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA; + + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + + /* Check it was deleted */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = 0; + io.in.fname = FNAME; + + torture_comment(tctx, "Testing if the file was deleted when closed\n"); + torture_comment(tctx, "We expect NT_STATUS_OBJECT_NAME_NOT_FOUND\n"); + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + return true; +} + +static bool test_doc_create_if_exist(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + NTSTATUS status; + uint32_t perms = 0; + + /* File should not exist for this first test, so make sure */ + set_dir_delete_perms(tctx, tree); + + smb2_deltree(tree, DNAME); + + create_dir(tctx, tree); + + torture_comment(tctx, "Create file with DeleteOnClose on existing file (OPEN_IF)\n"); + torture_comment(tctx, "We expect NT_STATUS_ACCESS_DENIED\n"); + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA; + + /* Create the file first */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = 0x0; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, io.out.file.handle); + + /* Now try to create it for delete on close */ + ZERO_STRUCT(io); + io.in.desired_access = 0x130196; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.in.fname = FNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + status = smb2_util_close(tree, io.out.file.handle); + + return true; +} + +static bool test_doc_find_and_set_doc(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_create io; + struct smb2_find find; + NTSTATUS status; + union smb_search_data *d; + union smb_setfileinfo sfinfo; + unsigned int count; + uint32_t perms = 0; + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA | SEC_DIR_LIST; + + /* File should not exist for this first test, so make sure */ + set_dir_delete_perms(tctx, tree); + + smb2_deltree(tree, DNAME); + + create_dir(tctx, tree); + + torture_comment(tctx, "FIND and delete directory\n"); + torture_comment(tctx, "We expect NT_STATUS_OK\n"); + + /* open the directory first */ + ZERO_STRUCT(io); + io.in.desired_access = perms; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.fname = DNAME; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + /* list directory */ + ZERO_STRUCT(find); + find.in.file.handle = io.out.file.handle; + find.in.pattern = "*"; + find.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; + find.in.max_response_size = 0x100; + find.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + /* start enumeration on directory */ + status = smb2_find_level(tree, tree, &find, &count, &d); + CHECK_STATUS(status, NT_STATUS_OK); + + /* set delete-on-close */ + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.disposition_info.in.delete_on_close = 1; + sfinfo.generic.in.file.handle = io.out.file.handle; + status = smb2_setinfo_file(tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* close directory */ + status = smb2_util_close(tree, io.out.file.handle); + CHECK_STATUS(status, NT_STATUS_OK); + return true; +} + +static bool test_doc_read_only(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle dir_handle; + union smb_setfileinfo sfinfo = {{0}}; + struct smb2_create create = {0}; + struct smb2_close close = {0}; + NTSTATUS status, expected_status; + bool ret = true, delete_readonly; + + /* + * Allow testing of the Samba 'delete readonly' option. + */ + delete_readonly = torture_setting_bool(tctx, "delete_readonly", false); + expected_status = delete_readonly ? + NT_STATUS_OK : NT_STATUS_CANNOT_DELETE; + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &dir_handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE directory failed\n"); + + create = (struct smb2_create) {0}; + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + create.in.file_attributes = FILE_ATTRIBUTE_READONLY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = FNAME; + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_equal_goto(tctx, status, expected_status, ret, + done, "Unexpected status for CREATE " + "of new file.\n"); + + if (delete_readonly) { + close.in.file.handle = create.out.file.handle; + status = smb2_close(tree, &close); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE of READONLY file " + "failed.\n"); + } + + torture_comment(tctx, "Creating file with READ_ONLY attribute.\n"); + + create = (struct smb2_create) {0}; + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + create.in.file_attributes = FILE_ATTRIBUTE_READONLY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = FNAME; + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE of READONLY file failed.\n"); + + close.in.file.handle = create.out.file.handle; + status = smb2_close(tree, &close); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE of READONLY file failed.\n"); + + torture_comment(tctx, "Testing CREATE with DELETE_ON_CLOSE on " + "READ_ONLY attribute file.\n"); + + create = (struct smb2_create) {0}; + create.in.desired_access = SEC_RIGHTS_FILE_READ | SEC_STD_DELETE; + create.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + create.in.file_attributes = 0; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.fname = FNAME; + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_equal_goto(tctx, status, + expected_status, ret, done, + "CREATE returned unexpected " + "status.\n"); + + torture_comment(tctx, "Testing setting DELETE_ON_CLOSE disposition on " + " file with READONLY attribute.\n"); + + create = (struct smb2_create) {0}; + create.in.desired_access = SEC_RIGHTS_FILE_READ | SEC_STD_DELETE;; + create.in.create_options = 0; + create.in.file_attributes = 0; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.fname = FNAME; + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Opening file failed.\n"); + + sfinfo.disposition_info.in.delete_on_close = 1; + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.generic.in.file.handle = create.out.file.handle; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_equal(tctx, status, expected_status, + "Set DELETE_ON_CLOSE disposition " + "returned un expected status.\n"); + + status = smb2_util_close(tree, create.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE failed\n"); + +done: + smb2_deltree(tree, DNAME); + return ret; +} + +/* + * This is a regression test for + * https://bugzilla.samba.org/show_bug.cgi?id=14427 + * + * It's not really a delete-on-close specific test. + */ +static bool test_doc_bug14427(struct torture_context *tctx, struct smb2_tree *tree1) +{ + struct smb2_tree *tree2 = NULL; + NTSTATUS status; + char fname[256]; + bool ret = false; + bool ok; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "doc_bug14427_%s.dat", + generate_random_str(tctx, 8)); + + ok = torture_smb2_tree_connect(tctx, tree1->session, tctx, &tree2); + torture_assert_goto(tctx, ok, ret, done, + "torture_smb2_tree_connect() failed.\n"); + + status = torture_setup_simple_file(tctx, tree1, fname); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_setup_simple_file() failed on tree1.\n"); + + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_unlink() failed on tree2.\n"); + TALLOC_FREE(tree2); + ret = true; +done: + if (tree2 != NULL) { + TALLOC_FREE(tree2); + smb2_util_unlink(tree1, fname); + } + + TALLOC_FREE(tree1); + return ret; +} + +/* + * Extreme testing of Delete On Close and permissions + */ +struct torture_suite *torture_smb2_doc_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "delete-on-close-perms"); + + torture_suite_add_1smb2_test(suite, "OVERWRITE_IF", test_doc_overwrite_if); + torture_suite_add_1smb2_test(suite, "OVERWRITE_IF Existing", test_doc_overwrite_if_exist); + torture_suite_add_1smb2_test(suite, "CREATE", test_doc_create); + torture_suite_add_1smb2_test(suite, "CREATE Existing", test_doc_create_exist); + torture_suite_add_1smb2_test(suite, "CREATE_IF", test_doc_create_if); + torture_suite_add_1smb2_test(suite, "CREATE_IF Existing", test_doc_create_if_exist); + torture_suite_add_1smb2_test(suite, "FIND_and_set_DOC", test_doc_find_and_set_doc); + torture_suite_add_1smb2_test(suite, "READONLY", test_doc_read_only); + torture_suite_add_1smb2_test(suite, "BUG14427", test_doc_bug14427); + + suite->description = talloc_strdup(suite, "SMB2-Delete-on-Close-Perms tests"); + + return suite; +} diff --git a/source4/torture/smb2/deny.c b/source4/torture/smb2/deny.c new file mode 100644 index 0000000..e42fd56 --- /dev/null +++ b/source4/torture/smb2/deny.c @@ -0,0 +1,526 @@ +/* + Unix SMB/CIFS implementation. + SMB2 torture tester - deny mode scanning functions + Copyright (C) Andrew Tridgell 2001 + Copyright (C) David Mulder 2019 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" + +enum deny_result {A_0=0, A_X=1, A_R=2, A_W=3, A_RW=5}; + +static const char *denystr(int denymode) +{ + const struct { + int v; + const char *name; + } deny_modes[] = { + {NTCREATEX_SHARE_ACCESS_NONE, "NTCREATEX_SHARE_ACCESS_NONE"}, + {NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, "NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE"}, + {NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, "NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE"}, + {NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, "NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE"}, + {-1, NULL}}; + int i; + for (i=0;deny_modes[i].name;i++) { + if (deny_modes[i].v == denymode) return deny_modes[i].name; + } + return "DENY_XXX"; +} + +static const char *openstr(int mode) +{ + const struct { + int v; + const char *name; + } open_modes[] = { + {SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, "SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA"}, + {SEC_FILE_READ_DATA, "SEC_FILE_READ_DATA"}, + {SEC_FILE_WRITE_DATA, "SEC_FILE_WRITE_DATA"}, + {-1, NULL}}; + int i; + for (i=0;open_modes[i].name;i++) { + if (open_modes[i].v == mode) return open_modes[i].name; + } + return "O_XXX"; +} + +static const char *resultstr(enum deny_result res) +{ + const struct { + enum deny_result res; + const char *name; + } results[] = { + {A_X, "X"}, + {A_0, "-"}, + {A_R, "R"}, + {A_W, "W"}, + {A_RW,"RW"}}; + int i; + for (i=0;i<ARRAY_SIZE(results);i++) { + if (results[i].res == res) return results[i].name; + } + return "*"; +} + +static const struct { + int isexe; + int mode1, deny1; + int mode2, deny2; + enum deny_result result; +} denytable[] = { +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{1, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_NONE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE, A_0}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_RW}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_READ_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_R}, +{0, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, SEC_FILE_WRITE_DATA, NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, A_W}, +}; + + +static void progress_bar(struct torture_context *tctx, unsigned int i, unsigned int total) +{ + if (torture_setting_bool(tctx, "progress", true)) { + torture_comment(tctx, "%5d/%5d\r", i, total); + fflush(stdout); + } +} + + +/* + this produces a matrix of deny mode behaviour with 2 connections + */ +static bool torture_smb2_denytest2(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + NTSTATUS status; + struct smb2_handle fnum1 = {{0}}, fnum2 = {{0}}; + int i; + bool correct = true; + const char *fnames[2] = {"denytest2.dat", "denytest2.exe"}; + struct timespec tv, tv_start; + + for (i=0;i<2;i++) { + struct smb2_create io = {0}; + smb2_util_unlink(tree1, fnames[i]); + io.in.fname = fnames[i]; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tree1, &io); + torture_assert_ntstatus_ok_goto(tctx, status, correct, failed, + talloc_asprintf(tctx, "Open of %s failed (%s)", + fnames[i], nt_errstr(status))); + fnum1 = io.out.file.handle; + smb2_util_write(tree1, fnum1, fnames[i], 0, strlen(fnames[i])); + smb2_util_close(tree1, fnum1); + } + + clock_gettime_mono(&tv_start); + + for (i=0; i<ARRAY_SIZE(denytable); i++) { + NTSTATUS s1, s2; + struct smb2_create io1 = {0}, io2 = {0}; + enum deny_result res; + const char *fname = fnames[denytable[i].isexe]; + + progress_bar(tctx, i, ARRAY_SIZE(denytable)); + + io1.in.fname = fname; + io1.in.desired_access = denytable[i].mode1; + io1.in.create_disposition = NTCREATEX_DISP_OPEN; + io1.in.share_access = denytable[i].deny1; + s1 = smb2_create(tree1, tree1, &io1); + fnum1 = io1.out.file.handle; + + io2.in.fname = fname; + io2.in.desired_access = denytable[i].mode2; + io2.in.create_disposition = NTCREATEX_DISP_OPEN; + io2.in.share_access = denytable[i].deny2; + s2 = smb2_create(tree2, tree2, &io2); + fnum2 = io2.out.file.handle; + + if (!NT_STATUS_IS_OK(s1)) { + res = A_X; + } else if (!NT_STATUS_IS_OK(s2)) { + res = A_0; + } else { + struct smb2_read io = {0}; + struct smb2_write wio = {0}; + uint8_t x = 1; + res = A_0; + + io.in.file.handle = fnum2; + io.in.length = 1; + status = smb2_read(tree2, tree2, &io); + if (NT_STATUS_IS_OK(status) && io.out.data.length == 1) { + res += A_R; + } + + wio.in.file.handle = fnum2; + wio.in.data = data_blob_const(&x, 1); + status = smb2_write(tree2, &wio); + if (NT_STATUS_IS_OK(status) && wio.out.nwritten == 1) { + res += A_W; + } + } + + if (torture_setting_bool(tctx, "showall", false) || + res != denytable[i].result) { + int64_t tdif; + clock_gettime_mono(&tv); + tdif = nsec_time_diff(&tv, &tv_start); + tdif /= 1000000; + torture_comment(tctx, "%lld: %s %8s %10s %8s %10s %s (correct=%s)\n", + (long long)tdif, + fname, + denystr(denytable[i].deny1), + openstr(denytable[i].mode1), + denystr(denytable[i].deny2), + openstr(denytable[i].mode2), + resultstr(res), + resultstr(denytable[i].result)); + } + + torture_assert_goto(tctx, res == denytable[i].result, + correct, failed, + talloc_asprintf(tctx, + "Result %s did not match deny table %s\n", + resultstr(res), + resultstr(denytable[i].result))); + + smb2_util_close(tree1, fnum1); + smb2_util_close(tree2, fnum2); + } + +failed: + for (i=0;i<2;i++) { + smb2_util_unlink(tree1, fnames[i]); + } + + return correct; +} + + +/* + this produces a matrix of deny mode behaviour for 1 connection + */ +static bool torture_smb2_denytest1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return torture_smb2_denytest2(tctx, tree, tree); +} + + +struct torture_suite *torture_smb2_deny_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "deny"); + + torture_suite_add_1smb2_test(suite, "deny1", torture_smb2_denytest1); + torture_suite_add_2smb2_test(suite, "deny2", torture_smb2_denytest2); + + suite->description = talloc_strdup(suite, "SMB2 deny tests"); + + return suite; +} diff --git a/source4/torture/smb2/dir.c b/source4/torture/smb2/dir.c new file mode 100644 index 0000000..3bd0f59 --- /dev/null +++ b/source4/torture/smb2/dir.c @@ -0,0 +1,1423 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 dir list test suite + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Zachary Loafman 2009 + Copyright (C) Aravind Srinivasan 2009 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb_composite/smb_composite.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/util.h" + +#include "system/filesys.h" +#include "lib/util/tsort.h" + +#define DNAME "smb2_dir" +#define NFILES 100 + +struct file_elem { + char *name; + NTTIME create_time; + bool found; +}; + +static NTSTATUS populate_tree(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct file_elem *files, + int nfiles, + struct smb2_handle *h_out) +{ + struct smb2_create create; + char **strs = NULL; + NTSTATUS status; + bool ret = true; + int i; + + smb2_deltree(tree, DNAME); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = DNAME; + + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + *h_out = create.out.file.handle; + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + strs = generate_unique_strs(mem_ctx, 8, nfiles); + if (strs == NULL) { + status = NT_STATUS_OBJECT_NAME_COLLISION; + goto done; + } + for (i = 0; i < nfiles; i++) { + files[i].name = strs[i]; + create.in.fname = talloc_asprintf(mem_ctx, "%s\\%s", + DNAME, files[i].name); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + files[i].create_time = create.out.create_time; + smb2_util_close(tree, create.out.file.handle); + } + done: + if (!ret) { + return status; + } + + return status; +} + +/* + test find continue +*/ + +static bool test_find(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle h; + struct smb2_find f; + union smb_search_data *d; + struct file_elem files[NFILES] = {}; + NTSTATUS status; + bool ret = true; + unsigned int count; + int i, j = 0, file_count = 0; + + status = populate_tree(tctx, mem_ctx, tree, files, NFILES, &h); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + for (i = 0; i < count; i++) { + bool expected; + const char *found = d[i].both_directory_info.name.s; + NTTIME ct = d[i].both_directory_info.create_time; + + if (!strcmp(found, ".") || !strcmp(found, "..")) + continue; + + expected = false; + for (j = 0; j < NFILES; j++) { + if (strcmp(files[j].name, found) != 0) { + continue; + } + + torture_assert_u64_equal_goto(tctx, + files[j].create_time, + ct, ret, done, + talloc_asprintf(tctx, + "file[%d]\n", j)); + + files[j].found = true; + expected = true; + break; + } + + if (expected) + continue; + + torture_result(tctx, TORTURE_FAIL, + "(%s): didn't expect %s\n", + __location__, found); + ret = false; + goto done; + } + + file_count = file_count + i; + f.in.continue_flags = 0; + f.in.max_response_size = 4096; + } while (count != 0); + + torture_assert_int_equal_goto(tctx, file_count, NFILES + 2, ret, done, + ""); + + for (i = 0; i < NFILES; i++) { + if (files[j].found) + continue; + + torture_result(tctx, TORTURE_FAIL, + "(%s): expected to find %s, but didn't\n", + __location__, files[j].name); + ret = false; + goto done; + } + + done: + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + test fixed enumeration +*/ + +static bool test_fixed(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create create; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_find f; + union smb_search_data *d; + struct file_elem files[NFILES] = {}; + NTSTATUS status; + bool ret = true; + unsigned int count; + int i; + + status = populate_tree(tctx, mem_ctx, tree, files, NFILES, &h); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.fname = DNAME; + + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + h2 = create.out.file.handle; + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + /* Start enumeration on h, then delete all from h2 */ + status = smb2_find_level(tree, tree, &f, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + f.in.file.handle = h2; + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + for (i = 0; i < count; i++) { + const char *found = d[i].both_directory_info.name.s; + char *path = talloc_asprintf(mem_ctx, "%s\\%s", + DNAME, found); + + if (!strcmp(found, ".") || !strcmp(found, "..")) + continue; + + status = smb2_util_unlink(tree, path); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + ""); + + talloc_free(path); + } + + f.in.continue_flags = 0; + f.in.max_response_size = 4096; + } while (count != 0); + + /* Now finish h enumeration. */ + f.in.file.handle = h; + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + for (i = 0; i < count; i++) { + const char *found = d[i].both_directory_info.name.s; + + if (!strcmp(found, ".") || !strcmp(found, "..")) + continue; + + torture_result(tctx, TORTURE_FAIL, + "(%s): didn't expect %s (count=%u)\n", + __location__, found, count); + ret = false; + goto done; + } + + f.in.continue_flags = 0; + f.in.max_response_size = 4096; + } while (count != 0); + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +static struct { + const char *name; + uint8_t level; + enum smb_search_data_level data_level; + int name_offset; + int resume_key_offset; + uint32_t capability_mask; + NTSTATUS status; + union smb_search_data data; +} levels[] = { + { + .name = "SMB2_FIND_DIRECTORY_INFO", + .level = SMB2_FIND_DIRECTORY_INFO, + .data_level = RAW_SEARCH_DATA_DIRECTORY_INFO, + .name_offset = offsetof(union smb_search_data, + directory_info.name.s), + .resume_key_offset = offsetof(union smb_search_data, + directory_info.file_index), + }, + { + .name = "SMB2_FIND_FULL_DIRECTORY_INFO", + .level = SMB2_FIND_FULL_DIRECTORY_INFO, + .data_level = RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, + .name_offset = offsetof(union smb_search_data, + full_directory_info.name.s), + .resume_key_offset = offsetof(union smb_search_data, + full_directory_info.file_index), + }, + { + .name = "SMB2_FIND_NAME_INFO", + .level = SMB2_FIND_NAME_INFO, + .data_level = RAW_SEARCH_DATA_NAME_INFO, + .name_offset = offsetof(union smb_search_data, + name_info.name.s), + .resume_key_offset = offsetof(union smb_search_data, + name_info.file_index), + }, + { + .name = "SMB2_FIND_BOTH_DIRECTORY_INFO", + .level = SMB2_FIND_BOTH_DIRECTORY_INFO, + .data_level = RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, + .name_offset = offsetof(union smb_search_data, + both_directory_info.name.s), + .resume_key_offset = offsetof(union smb_search_data, + both_directory_info.file_index), + }, + { + .name = "SMB2_FIND_ID_FULL_DIRECTORY_INFO", + .level = SMB2_FIND_ID_FULL_DIRECTORY_INFO, + .data_level = RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, + .name_offset = offsetof(union smb_search_data, + id_full_directory_info.name.s), + .resume_key_offset = offsetof(union smb_search_data, + id_full_directory_info.file_index), + }, + { + .name = "SMB2_FIND_ID_BOTH_DIRECTORY_INFO", + .level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, + .data_level = RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, + .name_offset = offsetof(union smb_search_data, + id_both_directory_info.name.s), + .resume_key_offset = offsetof(union smb_search_data, + id_both_directory_info.file_index), + } +}; + +/* + extract the name from a smb_data structure and level +*/ +static const char *extract_name(union smb_search_data *data, + uint8_t level, + enum smb_search_data_level data_level) +{ + int i; + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (level == levels[i].level && + data_level == levels[i].data_level) { + return *(const char **)(levels[i].name_offset + (char *)data); + } + } + return NULL; +} + +/* find a level in the table by name */ +static union smb_search_data *find(const char *name) +{ + int i; + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (NT_STATUS_IS_OK(levels[i].status) && + strcmp(levels[i].name, name) == 0) { + return &levels[i].data; + } + } + return NULL; +} + +static bool fill_level_data(TALLOC_CTX *mem_ctx, + union smb_search_data *data, + union smb_search_data *d, + unsigned int count, + uint8_t level, + enum smb_search_data_level data_level) +{ + int i; + const char *sname = NULL; + for (i=0; i < count ; i++) { + sname = extract_name(&d[i], level, data_level); + if (sname == NULL) + return false; + if (!strcmp(sname, ".") || !strcmp(sname, "..")) + continue; + *data = d[i]; + } + return true; +} + + +NTSTATUS torture_single_file_search(struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + const char *pattern, + uint8_t level, + enum smb_search_data_level data_level, + int idx, + union smb_search_data *d, + unsigned int *count, + struct smb2_handle *h) +{ + struct smb2_find f; + NTSTATUS status; + + ZERO_STRUCT(f); + f.in.file.handle = *h; + f.in.pattern = pattern; + f.in.continue_flags = SMB2_CONTINUE_FLAG_RESTART; + f.in.max_response_size = 0x100; + f.in.level = level; + + status = smb2_find_level(tree, tree, &f, count, &d); + if (NT_STATUS_IS_OK(status)) + fill_level_data(mem_ctx, &levels[idx].data, d, *count, level, + data_level); + return status; +} + +/* + basic testing of all File Information Classes using a single file +*/ +static bool test_one_file(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + bool ret = true; + const char *fname = "torture_search.txt"; + NTSTATUS status; + int i; + unsigned int count; + union smb_fileinfo all_info2, alt_info, internal_info; + union smb_search_data *s; + union smb_search_data d; + struct smb2_handle h, h2; + + status = torture_smb2_testdir(tree, DNAME, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + status = smb2_create_complex_file(tctx, tree, DNAME "\\torture_search.txt", + &h2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + /* call all the File Information Classes */ + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing %s %d\n", levels[i].name, + levels[i].level); + + levels[i].status = torture_single_file_search(tree, mem_ctx, + fname, levels[i].level, levels[i].data_level, + i, &d, &count, &h); + torture_assert_ntstatus_ok_goto(tctx, levels[i].status, ret, + done, ""); + } + + /* get the all_info file into to check against */ + all_info2.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + all_info2.generic.in.file.handle = h2; + status = smb2_getinfo_file(tree, tctx, &all_info2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "RAW_FILEINFO_ALL_INFO failed"); + + alt_info.generic.level = RAW_FILEINFO_ALT_NAME_INFORMATION; + alt_info.generic.in.file.handle = h2; + status = smb2_getinfo_file(tree, tctx, &alt_info); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "RAW_FILEINFO_ALT_NAME_INFO failed"); + + internal_info.generic.level = RAW_FILEINFO_INTERNAL_INFORMATION; + internal_info.generic.in.file.handle = h2; + status = smb2_getinfo_file(tree, tctx, &internal_info); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "RAW_FILEINFO_INTERNAL_INFORMATION " + "failed"); + +#define CHECK_VAL(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if ((s->sname1.field1) != (v.sname2.out.field2)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s/%s [0x%x] != %s/%s [0x%x]\n", \ + __location__, \ + #sname1, #field1, (int)s->sname1.field1, \ + #sname2, #field2, (int)v.sname2.out.field2); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_TIME(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if (s->sname1.field1 != \ + (~1 & nt_time_to_unix(v.sname2.out.field2))) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, \ + timestring(tctx, s->sname1.field1), \ + #sname2, #field2, \ + nt_time_string(tctx, v.sname2.out.field2)); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_NTTIME(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if (s->sname1.field1 != v.sname2.out.field2) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, \ + nt_time_string(tctx, s->sname1.field1), \ + #sname2, #field2, \ + nt_time_string(tctx, v.sname2.out.field2)); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_STR(name, sname1, field1, v, sname2, field2) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1 || \ + strcmp(s->sname1.field1, v.sname2.out.field2.s)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1, \ + #sname2, #field2, v.sname2.out.field2.s); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_WSTR(name, sname1, field1, v, sname2, field2, flags) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1.s || \ + strcmp(s->sname1.field1.s, v.sname2.out.field2.s)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s/%s [%s] != %s/%s [%s]\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1.s, \ + #sname2, #field2, v.sname2.out.field2.s); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_NAME(name, sname1, field1, fname, flags) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1.s || \ + strcmp(s->sname1.field1.s, fname)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s/%s [%s] != %s\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1.s, fname); \ + ret = false; \ + } \ + }} while (0) + +#define CHECK_UNIX_NAME(name, sname1, field1, fname, flags) do { \ + s = find(name); \ + if (s) { \ + if (!s->sname1.field1 || \ + strcmp(s->sname1.field1, fname)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s/%s [%s] != %s\n", \ + __location__, \ + #sname1, #field1, s->sname1.field1, fname); \ + ret = false; \ + } \ + }} while (0) + + /* check that all the results are as expected */ + CHECK_VAL("SMB2_FIND_DIRECTORY_INFO", directory_info, attrib, all_info2, all_info2, attrib); + CHECK_VAL("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, attrib, all_info2, all_info2, attrib); + CHECK_VAL("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, attrib, all_info2, all_info2, attrib); + CHECK_VAL("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, attrib, all_info2, all_info2, attrib); + CHECK_VAL("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, attrib, all_info2, all_info2, attrib); + + CHECK_NTTIME("SMB2_FIND_DIRECTORY_INFO", directory_info, write_time, all_info2, all_info2, write_time); + CHECK_NTTIME("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, write_time, all_info2, all_info2, write_time); + CHECK_NTTIME("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, write_time, all_info2, all_info2, write_time); + CHECK_NTTIME("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, write_time, all_info2, all_info2, write_time); + CHECK_NTTIME("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, write_time, all_info2, all_info2, write_time); + + CHECK_NTTIME("SMB2_FIND_DIRECTORY_INFO", directory_info, create_time, all_info2, all_info2, create_time); + CHECK_NTTIME("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, create_time, all_info2, all_info2, create_time); + CHECK_NTTIME("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, create_time, all_info2, all_info2, create_time); + CHECK_NTTIME("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, create_time, all_info2, all_info2, create_time); + CHECK_NTTIME("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, create_time, all_info2, all_info2, create_time); + + CHECK_NTTIME("SMB2_FIND_DIRECTORY_INFO", directory_info, access_time, all_info2, all_info2, access_time); + CHECK_NTTIME("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, access_time, all_info2, all_info2, access_time); + CHECK_NTTIME("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, access_time, all_info2, all_info2, access_time); + CHECK_NTTIME("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, access_time, all_info2, all_info2, access_time); + CHECK_NTTIME("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, access_time, all_info2, all_info2, access_time); + + CHECK_NTTIME("SMB2_FIND_DIRECTORY_INFO", directory_info, change_time, all_info2, all_info2, change_time); + CHECK_NTTIME("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, change_time, all_info2, all_info2, change_time); + CHECK_NTTIME("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, change_time, all_info2, all_info2, change_time); + CHECK_NTTIME("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, change_time, all_info2, all_info2, change_time); + CHECK_NTTIME("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, change_time, all_info2, all_info2, change_time); + + CHECK_VAL("SMB2_FIND_DIRECTORY_INFO", directory_info, size, all_info2, all_info2, size); + CHECK_VAL("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, size, all_info2, all_info2, size); + CHECK_VAL("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, size, all_info2, all_info2, size); + CHECK_VAL("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, size, all_info2, all_info2, size); + CHECK_VAL("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, size, all_info2, all_info2, size); + + CHECK_VAL("SMB2_FIND_DIRECTORY_INFO", directory_info, alloc_size, all_info2, all_info2, alloc_size); + CHECK_VAL("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, alloc_size, all_info2, all_info2, alloc_size); + CHECK_VAL("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, alloc_size, all_info2, all_info2, alloc_size); + CHECK_VAL("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, alloc_size, all_info2, all_info2, alloc_size); + CHECK_VAL("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, alloc_size, all_info2, all_info2, alloc_size); + + CHECK_VAL("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, ea_size, all_info2, all_info2, ea_size); + CHECK_VAL("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, ea_size, all_info2, all_info2, ea_size); + CHECK_VAL("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, ea_size, all_info2, all_info2, ea_size); + CHECK_VAL("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, ea_size, all_info2, all_info2, ea_size); + + CHECK_NAME("SMB2_FIND_DIRECTORY_INFO", directory_info, name, fname, STR_TERMINATE_ASCII); + CHECK_NAME("SMB2_FIND_FULL_DIRECTORY_INFO", full_directory_info, name, fname, STR_TERMINATE_ASCII); + CHECK_NAME("SMB2_FIND_NAME_INFO", name_info, name, fname, STR_TERMINATE_ASCII); + CHECK_NAME("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, name, fname, STR_TERMINATE_ASCII); + CHECK_NAME("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, name, fname, STR_TERMINATE_ASCII); + CHECK_NAME("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, name, fname, STR_TERMINATE_ASCII); + + CHECK_WSTR("SMB2_FIND_BOTH_DIRECTORY_INFO", both_directory_info, short_name, alt_info, alt_name_info, fname, STR_UNICODE); + + CHECK_VAL("SMB2_FIND_ID_FULL_DIRECTORY_INFO", id_full_directory_info, file_id, internal_info, internal_information, file_id); + CHECK_VAL("SMB2_FIND_ID_BOTH_DIRECTORY_INFO", id_both_directory_info, file_id, internal_info, internal_information, file_id); + +done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + + return ret; +} + + +struct multiple_result { + TALLOC_CTX *tctx; + int count; + union smb_search_data *list; +}; + +bool fill_result(void *private_data, + union smb_search_data *file, + int count, + uint8_t level, + enum smb_search_data_level data_level) +{ + int i; + const char *sname; + struct multiple_result *data = (struct multiple_result *)private_data; + + for (i=0; i<count; i++) { + sname = extract_name(&file[i], level, data_level); + if (!strcmp(sname, ".") || !(strcmp(sname, ".."))) + continue; + data->count++; + data->list = talloc_realloc(data->tctx, + data->list, + union smb_search_data, + data->count); + data->list[data->count-1] = file[i]; + } + return true; +} + +enum continue_type {CONT_SINGLE, CONT_INDEX, CONT_RESTART, CONT_REOPEN}; + +static NTSTATUS multiple_smb2_search(struct smb2_tree *tree, + TALLOC_CTX *tctx, + const char *pattern, + uint8_t level, + enum smb_search_data_level data_level, + enum continue_type cont_type, + void *data, + struct smb2_handle *h) +{ + struct smb2_find f; + bool ret = true; + unsigned int count = 0; + union smb_search_data *d; + NTSTATUS status; + struct multiple_result *result = (struct multiple_result *)data; + + ZERO_STRUCT(f); + f.in.file.handle = *h; + f.in.pattern = pattern; + f.in.max_response_size = 0x1000; + f.in.level = level; + + /* The search should start from the beginning everytime */ + f.in.continue_flags = SMB2_CONTINUE_FLAG_RESTART; + if (cont_type == CONT_REOPEN) { + f.in.continue_flags = SMB2_CONTINUE_FLAG_REOPEN; + } + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + if (!fill_result(result, d, count, level, data_level)) { + return NT_STATUS_UNSUCCESSFUL; + } + + if (count == 0 || result == NULL || result->count == 0) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* + * After the first iteration is complete set the CONTINUE + * FLAGS appropriately + */ + switch (cont_type) { + case CONT_INDEX: + f.in.continue_flags = SMB2_CONTINUE_FLAG_INDEX; + switch (data_level) { + case RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO: + f.in.file_index = + result->list[result->count-1].both_directory_info.file_index; + break; + case RAW_SEARCH_DATA_DIRECTORY_INFO: + f.in.file_index = + result->list[result->count-1].directory_info.file_index; + break; + case RAW_SEARCH_DATA_FULL_DIRECTORY_INFO: + f.in.file_index = + result->list[result->count-1].full_directory_info.file_index; + break; + case RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO: + f.in.file_index = + result->list[result->count-1].id_full_directory_info.file_index; + break; + case RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO: + f.in.file_index = + result->list[result->count-1].id_both_directory_info.file_index; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + break; + case CONT_SINGLE: + f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; + break; + case CONT_RESTART: + default: + /* we should prevent staying in the loop + * forever */ + f.in.continue_flags = 0; + break; + } + } while (count != 0); +done: + if (!ret) { + return status; + } + return status; +} + + +static enum smb_search_data_level compare_data_level; +uint8_t level_sort; + +static int search_compare(union smb_search_data *d1, + union smb_search_data *d2) +{ + const char *s1, *s2; + + s1 = extract_name(d1, level_sort, compare_data_level); + s2 = extract_name(d2, level_sort, compare_data_level); + return strcmp_safe(s1, s2); +} + +/* + basic testing of search calls using many files +*/ +static bool test_many_files(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const int num_files = 700; + int i, t; + char *fname; + bool ret = true; + NTSTATUS status; + struct multiple_result result; + struct smb2_create create; + struct smb2_handle h; + struct { + const char *name; + const char *cont_name; + uint8_t level; + enum smb_search_data_level data_level; + enum continue_type cont_type; + } search_types[] = { + {"SMB2_FIND_BOTH_DIRECTORY_INFO", "SINGLE", SMB2_FIND_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, CONT_SINGLE}, + {"SMB2_FIND_BOTH_DIRECTORY_INFO", "INDEX", SMB2_FIND_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, CONT_INDEX}, + {"SMB2_FIND_BOTH_DIRECTORY_INFO", "RESTART", SMB2_FIND_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, CONT_RESTART}, + {"SMB2_FIND_BOTH_DIRECTORY_INFO", "REOPEN", SMB2_FIND_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, CONT_REOPEN}, + {"SMB2_FIND_DIRECTORY_INFO", "SINGLE", SMB2_FIND_DIRECTORY_INFO, RAW_SEARCH_DATA_DIRECTORY_INFO, CONT_SINGLE}, + {"SMB2_FIND_DIRECTORY_INFO", "INDEX", SMB2_FIND_DIRECTORY_INFO, RAW_SEARCH_DATA_DIRECTORY_INFO, CONT_INDEX}, + {"SMB2_FIND_DIRECTORY_INFO", "RESTART", SMB2_FIND_DIRECTORY_INFO, RAW_SEARCH_DATA_DIRECTORY_INFO, CONT_RESTART}, + {"SMB2_FIND_DIRECTORY_INFO", "REOPEN", SMB2_FIND_DIRECTORY_INFO, RAW_SEARCH_DATA_DIRECTORY_INFO, CONT_REOPEN}, + {"SMB2_FIND_FULL_DIRECTORY_INFO", "SINGLE", SMB2_FIND_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, CONT_SINGLE}, + {"SMB2_FIND_FULL_DIRECTORY_INFO", "INDEX", SMB2_FIND_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, CONT_INDEX}, + {"SMB2_FIND_FULL_DIRECTORY_INFO", "RESTART", SMB2_FIND_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, CONT_RESTART}, + {"SMB2_FIND_FULL_DIRECTORY_INFO", "REOPEN", SMB2_FIND_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_FULL_DIRECTORY_INFO, CONT_REOPEN}, + {"SMB2_FIND_ID_FULL_DIRECTORY_INFO", "SINGLE", SMB2_FIND_ID_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, CONT_SINGLE}, + {"SMB2_FIND_ID_FULL_DIRECTORY_INFO", "INDEX", SMB2_FIND_ID_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, CONT_INDEX}, + {"SMB2_FIND_ID_FULL_DIRECTORY_INFO", "RESTART", SMB2_FIND_ID_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, CONT_RESTART}, + {"SMB2_FIND_ID_FULL_DIRECTORY_INFO", "REOPEN", SMB2_FIND_ID_FULL_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO, CONT_REOPEN}, + {"SMB2_FIND_ID_BOTH_DIRECTORY_INFO", "SINGLE", SMB2_FIND_ID_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, CONT_SINGLE}, + {"SMB2_FIND_ID_BOTH_DIRECTORY_INFO", "INDEX", SMB2_FIND_ID_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, CONT_INDEX}, + {"SMB2_FIND_ID_BOTH_DIRECTORY_INFO", "RESTART", SMB2_FIND_ID_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, CONT_RESTART}, + {"SMB2_FIND_ID_BOTH_DIRECTORY_INFO", "REOPEN", SMB2_FIND_ID_BOTH_DIRECTORY_INFO, RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO, CONT_REOPEN}, + }; + + smb2_deltree(tree, DNAME); + status = torture_smb2_testdir(tree, DNAME, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + torture_comment(tctx, "Testing with %d files\n", num_files); + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + for (i=num_files-1;i>=0;i--) { + fname = talloc_asprintf(mem_ctx, DNAME "\\t%03d-%d.txt", i, i); + create.in.fname = talloc_asprintf(mem_ctx, "%s", fname); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + smb2_util_close(tree, create.out.file.handle); + talloc_free(fname); + } + + for (t=0;t<ARRAY_SIZE(search_types);t++) { + ZERO_STRUCT(result); + result.tctx = talloc_new(tctx); + + torture_comment(tctx, + "Continue %s via %s\n", search_types[t].name, + search_types[t].cont_name); + status = multiple_smb2_search(tree, tctx, "*", + search_types[t].level, + search_types[t].data_level, + search_types[t].cont_type, + &result, &h); + + torture_assert_int_equal_goto(tctx, result.count, num_files, + ret, done, ""); + + compare_data_level = search_types[t].data_level; + level_sort = search_types[t].level; + + TYPESAFE_QSORT(result.list, result.count, search_compare); + + for (i=0;i<result.count;i++) { + const char *s; + s = extract_name(&result.list[i], + search_types[t].level, + compare_data_level); + fname = talloc_asprintf(mem_ctx, "t%03d-%d.txt", i, i); + torture_assert_str_equal_goto(tctx, s, fname, ret, + done, "Incorrect name"); + talloc_free(fname); + } + talloc_free(result.tctx); + } + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + check an individual file result +*/ +static bool check_result(struct torture_context *tctx, + struct multiple_result *result, + const char *name, + bool exist, + uint32_t attrib) +{ + int i; + for (i=0;i<result->count;i++) { + if (strcmp(name, + result->list[i].both_directory_info.name.s) == 0) { + break; + } + } + if (i == result->count) { + if (exist) { + torture_result(tctx, TORTURE_FAIL, + "failed: '%s' should exist with attribute %s\n", + name, attrib_string(result->list, attrib)); + return false; + } + return true; + } + + if (!exist) { + torture_result(tctx, TORTURE_FAIL, + "failed: '%s' should NOT exist (has attribute %s)\n", + name, attrib_string(result->list, + result->list[i].both_directory_info.attrib)); + return false; + } + + if ((result->list[i].both_directory_info.attrib&0xFFF) != attrib) { + torture_result(tctx, TORTURE_FAIL, + "failed: '%s' should have attribute 0x%x (has 0x%x)\n", + name, attrib, result->list[i].both_directory_info.attrib); + return false; + } + return true; +} + +/* + test what happens when the directory is modified during a search +*/ +static bool test_modify_search(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct multiple_result result; + union smb_setfileinfo sfinfo; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create create; + struct smb2_handle h; + struct smb2_find f; + union smb_search_data *d; + struct file_elem files[703] = {}; + int num_files = ARRAY_SIZE(files)-3; + NTSTATUS status; + bool ret = true; + int i; + unsigned int count; + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + torture_comment(tctx, "Creating %d files\n", num_files); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + for (i = num_files-1; i >= 0; i--) { + files[i].name = talloc_asprintf(mem_ctx, "t%03d-%d.txt", i, i); + create.in.fname = talloc_asprintf(mem_ctx, "%s\\%s", + DNAME, files[i].name); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + smb2_util_close(tree, create.out.file.handle); + } + + torture_comment(tctx, "pulling the first two files\n"); + ZERO_STRUCT(result); + result.tctx = talloc_new(tctx); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + if (!fill_result(&result, d, count, f.in.level, + RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO)) { + ret = false; + goto done; + } + } while (result.count < 2); + + torture_comment(tctx, "Changing attributes and deleting\n"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + files[num_files].name = talloc_asprintf(mem_ctx, "T003-03.txt.2"); + create.in.fname = talloc_asprintf(mem_ctx, "%s\\%s", DNAME, + files[num_files].name); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + smb2_util_close(tree, create.out.file.handle); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + files[num_files + 1].name = talloc_asprintf(mem_ctx, "T013-13.txt.2"); + create.in.fname = talloc_asprintf(mem_ctx, "%s\\%s", DNAME, + files[num_files + 1].name); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + smb2_util_close(tree, create.out.file.handle); + + files[num_files + 2].name = talloc_asprintf(mem_ctx, "T013-13.txt.3"); + status = smb2_create_complex_file(tctx, tree, DNAME "\\T013-13.txt.3", &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + smb2_util_unlink(tree, DNAME "\\T014-14.txt"); + smb2_util_setatr(tree, DNAME "\\T015-15.txt", FILE_ATTRIBUTE_HIDDEN); + smb2_util_setatr(tree, DNAME "\\T016-16.txt", FILE_ATTRIBUTE_NORMAL); + smb2_util_setatr(tree, DNAME "\\T017-17.txt", FILE_ATTRIBUTE_SYSTEM); + smb2_util_setatr(tree, DNAME "\\T018-18.txt", 0); + smb2_util_setatr(tree, DNAME "\\T039-39.txt", FILE_ATTRIBUTE_HIDDEN); + smb2_util_setatr(tree, DNAME "\\T000-0.txt", FILE_ATTRIBUTE_HIDDEN); + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.generic.in.file.path = DNAME "\\T013-13.txt.3"; + sfinfo.disposition_info.in.delete_on_close = 1; + status = smb2_composite_setpathinfo(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + /* Reset the numfiles to include the new files and start the + * search from the beginning */ + num_files = num_files + 2; + f.in.pattern = "*"; + f.in.continue_flags = SMB2_CONTINUE_FLAG_RESTART; + result.count = 0; + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + if (!fill_result(&result, d, count, f.in.level, + RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO)) { + ret = false; + goto done; + } + f.in.continue_flags = 0; + f.in.max_response_size = 4096; + } while (count != 0); + + + ret &= check_result(tctx, &result, "t039-39.txt", true, FILE_ATTRIBUTE_HIDDEN); + ret &= check_result(tctx, &result, "t000-0.txt", true, FILE_ATTRIBUTE_HIDDEN); + ret &= check_result(tctx, &result, "t014-14.txt", false, 0); + ret &= check_result(tctx, &result, "t015-15.txt", true, FILE_ATTRIBUTE_HIDDEN); + ret &= check_result(tctx, &result, "t016-16.txt", true, FILE_ATTRIBUTE_NORMAL); + ret &= check_result(tctx, &result, "t017-17.txt", true, FILE_ATTRIBUTE_SYSTEM); + ret &= check_result(tctx, &result, "t018-18.txt", true, FILE_ATTRIBUTE_ARCHIVE); + ret &= check_result(tctx, &result, "t019-19.txt", true, FILE_ATTRIBUTE_ARCHIVE); + ret &= check_result(tctx, &result, "T013-13.txt.2", true, FILE_ATTRIBUTE_ARCHIVE); + ret &= check_result(tctx, &result, "T003-3.txt.2", false, 0); + ret &= check_result(tctx, &result, "T013-13.txt.3", true, FILE_ATTRIBUTE_NORMAL); + + if (!ret) { + for (i=0;i<result.count;i++) { + torture_warning(tctx, "%s %s (0x%x)\n", + result.list[i].both_directory_info.name.s, + attrib_string(tctx, + result.list[i].both_directory_info.attrib), + result.list[i].both_directory_info.attrib); + } + } + done: + smb2_util_close(tree, h); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + testing if directories always come back sorted +*/ +static bool test_sorted(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const int num_files = 700; + int i; + struct file_elem files[700] = {}; + bool ret = true; + NTSTATUS status; + struct multiple_result result; + struct smb2_handle h; + + torture_comment(tctx, "Testing if directories always come back " + "sorted\n"); + status = populate_tree(tctx, mem_ctx, tree, files, num_files, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + ZERO_STRUCT(result); + result.tctx = tctx; + + status = multiple_smb2_search(tree, tctx, "*", + SMB2_FIND_BOTH_DIRECTORY_INFO, + RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO, + SMB2_CONTINUE_FLAG_SINGLE, + &result, &h); + + torture_assert_int_equal_goto(tctx, result.count, num_files, ret, done, + ""); + + for (i=0;i<num_files-1;i++) { + const char *name1, *name2; + name1 = result.list[i].both_directory_info.name.s; + name2 = result.list[i+1].both_directory_info.name.s; + if (strcasecmp_m(name1, name2) > 0) { + torture_comment(tctx, "non-alphabetical order at entry " + "%d '%s' '%s'\n", i, name1, name2); + torture_comment(tctx, + "Server does not produce sorted directory listings" + "(not an error)\n"); + goto done; + } + } + talloc_free(result.list); +done: + smb2_util_close(tree, h); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* test the behavior of file_index field in the SMB2_FIND struct */ +static bool test_file_index(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const int num_files = 100; + int resume_index = 4; + int i; + char *fname; + bool ret = true; + NTSTATUS status; + struct multiple_result result; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h; + union smb_search_data *d; + unsigned count; + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + torture_comment(tctx, "Testing the behavior of file_index flag\n"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + for (i = num_files-1; i >= 0; i--) { + fname = talloc_asprintf(mem_ctx, DNAME "\\file%u.txt", i); + create.in.fname = fname; + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + talloc_free(fname); + smb2_util_close(tree, create.out.file.handle); + } + + ZERO_STRUCT(result); + result.tctx = tctx; + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; + f.in.max_response_size = 0x1000; + f.in.level = SMB2_FIND_FULL_DIRECTORY_INFO; + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + if (!fill_result(&result, d, count, f.in.level, + RAW_SEARCH_DATA_FULL_DIRECTORY_INFO)) { + ret = false; + goto done; + } + } while(result.count < 10); + + if (result.list[0].full_directory_info.file_index == 0) { + torture_skip_goto(tctx, done, + "Talking to a server that doesn't provide a " + "file index.\nWindows servers using NTFS do " + "not provide a file_index. Skipping test\n"); + } else { + /* We are not talking to a Windows based server. Windows + * servers using NTFS do not provide a file_index. Windows + * servers using FAT do provide a file index, however in both + * cases they do not honor a file index on a resume request. + * See MS-FSCC <62> and MS-SMB2 <54> for more information. */ + + /* Set the file_index flag to point to the fifth file from the + * previous enumeration and try to start the subsequent + * searches from that point */ + f.in.file_index = + result.list[resume_index].full_directory_info.file_index; + f.in.continue_flags = SMB2_CONTINUE_FLAG_INDEX; + + /* get the name of the next expected file */ + fname = talloc_asprintf(mem_ctx, DNAME "\\%s", + result.list[resume_index].full_directory_info.name.s); + + ZERO_STRUCT(result); + result.tctx = tctx; + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + goto done; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + if (!fill_result(&result, d, count, f.in.level, + RAW_SEARCH_DATA_FULL_DIRECTORY_INFO)) { + ret = false; + goto done; + } + if (strcmp(fname, + result.list[0].full_directory_info.name.s)) { + torture_comment(tctx, "Next expected file: %s but the " + "server returned %s\n", fname, + result.list[0].full_directory_info.name.s); + torture_comment(tctx, + "Not an error. Resuming using a file " + "index is an optional feature of the " + "protocol.\n"); + goto done; + } + } +done: + smb2_util_close(tree, h); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + * Tests directory enumeration in a directory containing >1000 files with + * names of varying lengths. + */ +static bool test_large_files(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const int num_files = 2000; + int max_len = 200; + /* These should be evenly divisible */ + int num_at_len = num_files / max_len; + struct file_elem files[2000] = {}; + size_t len = 1; + bool ret = true; + NTSTATUS status; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h = {{0}}; + union smb_search_data *d; + int i, j = 0, file_count = 0; + char **strs = NULL; + unsigned count; + + torture_comment(tctx, + "Testing directory enumeration in a directory with >1000 files\n"); + + smb2_deltree(tree, DNAME); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = DNAME; + + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + h = create.out.file.handle; + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + for (i = 0; i < num_files; i++) { + if (i % num_at_len == 0) { + strs = generate_unique_strs(mem_ctx, len, num_at_len); + len++; + } + files[i].name = strs[i % num_at_len]; + create.in.fname = talloc_asprintf(mem_ctx, "%s\\%s", + DNAME, files[i].name); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + smb2_util_close(tree, create.out.file.handle); + } + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + do { + status = smb2_find_level(tree, tree, &f, &count, &d); + if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) + break; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + for (i = 0; i < count; i++) { + bool expected; + const char *found = d[i].both_directory_info.name.s; + + if (!strcmp(found, ".") || !strcmp(found, "..")) + continue; + + expected = false; + for (j = 0; j < 2000; j++) { + if (!strcmp(files[j].name, found)) { + files[j].found = true; + expected = true; + break; + } + } + + if (expected) + continue; + + torture_result(tctx, TORTURE_FAIL, + "(%s): didn't expect %s\n", + __location__, found); + ret = false; + goto done; + } + file_count = file_count + i; + f.in.continue_flags = 0; + f.in.max_response_size = 4096; + } while (count != 0); + + torture_assert_int_equal_goto(tctx, file_count, num_files + 2, ret, + done, ""); + + for (i = 0; i < num_files; i++) { + if (files[j].found) + continue; + + torture_result(tctx, TORTURE_FAIL, + "(%s): expected to find %s, but didn't\n", + __location__, files[j].name); + ret = false; + goto done; + } +done: + smb2_util_close(tree, h); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +struct torture_suite *torture_smb2_dir_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "dir"); + + torture_suite_add_1smb2_test(suite, "find", test_find); + torture_suite_add_1smb2_test(suite, "fixed", test_fixed); + torture_suite_add_1smb2_test(suite, "one", test_one_file); + torture_suite_add_1smb2_test(suite, "many", test_many_files); + torture_suite_add_1smb2_test(suite, "modify", test_modify_search); + torture_suite_add_1smb2_test(suite, "sorted", test_sorted); + torture_suite_add_1smb2_test(suite, "file-index", test_file_index); + torture_suite_add_1smb2_test(suite, "large-files", test_large_files); + suite->description = talloc_strdup(suite, "SMB2-DIR tests"); + + return suite; +} diff --git a/source4/torture/smb2/dosmode.c b/source4/torture/smb2/dosmode.c new file mode 100644 index 0000000..7610a20 --- /dev/null +++ b/source4/torture/smb2/dosmode.c @@ -0,0 +1,254 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 setinfo individual test suite + + Copyright (C) Ralph Boehme 2016 + + 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/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +/* + test dosmode and hidden files +*/ +bool torture_smb2_dosmode(struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + struct smb2_tree *tree = NULL; + const char *dname = "torture_dosmode"; + const char *fname = "torture_dosmode\\file"; + const char *hidefile = "torture_dosmode\\hidefile"; + const char *dotfile = "torture_dosmode\\.dotfile"; + struct smb2_handle h1 = {{0}}; + struct smb2_create io; + union smb_setfileinfo sfinfo; + union smb_fileinfo finfo2; + + torture_comment(tctx, "Checking dosmode with \"hide files\" " + "and \"hide dot files\"\n"); + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + + smb2_deltree(tree, dname); + + status = torture_smb2_testdir(tree, dname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed"); + + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = 0; + io.in.fname = fname; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + + ZERO_STRUCT(sfinfo); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_HIDDEN; + sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sfinfo.generic.in.file.handle = io.out.file.handle; + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_filefailed"); + + ZERO_STRUCT(finfo2); + finfo2.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + finfo2.generic.in.file.handle = io.out.file.handle; + status = smb2_getinfo_file(tree, tctx, &finfo2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + torture_assert_int_equal_goto(tctx, finfo2.all_info2.out.attrib & FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_HIDDEN, ret, done, + "FILE_ATTRIBUTE_HIDDEN is not set"); + + smb2_util_close(tree, io.out.file.handle); + + /* This must fail with attribute mismatch */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.create_options = 0; + io.in.fname = fname; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done,"smb2_create failed"); + + /* Create a file in "hide files" */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = 0; + io.in.fname = hidefile; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + + ZERO_STRUCT(finfo2); + finfo2.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + finfo2.generic.in.file.handle = io.out.file.handle; + status = smb2_getinfo_file(tree, tctx, &finfo2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + torture_assert_int_equal_goto(tctx, finfo2.all_info2.out.attrib & FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_HIDDEN, ret, done, + "FILE_ATTRIBUTE_HIDDEN is not set"); + + smb2_util_close(tree, io.out.file.handle); + + /* Overwrite a file in "hide files", should pass */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.create_options = 0; + io.in.fname = hidefile; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + smb2_util_close(tree, io.out.file.handle); + + /* Create a "hide dot files" */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = 0; + io.in.fname = dotfile; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + + ZERO_STRUCT(finfo2); + finfo2.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + finfo2.generic.in.file.handle = io.out.file.handle; + status = smb2_getinfo_file(tree, tctx, &finfo2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + torture_assert_int_equal_goto(tctx, finfo2.all_info2.out.attrib & FILE_ATTRIBUTE_HIDDEN, + FILE_ATTRIBUTE_HIDDEN, ret, done, + "FILE_ATTRIBUTE_HIDDEN is not set"); + + smb2_util_close(tree, io.out.file.handle); + + /* Overwrite a "hide dot files", should pass */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.create_options = 0; + io.in.fname = dotfile; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + smb2_util_close(tree, io.out.file.handle); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, dname); + return ret; +} + +bool torture_smb2_async_dosmode(struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + struct smb2_tree *tree = NULL; + const char *dname = "torture_dosmode"; + const char *fname = "torture_dosmode\\file"; + struct smb2_handle h = {{0}}; + struct smb2_create io; + union smb_setfileinfo sfinfo; + struct smb2_find f; + union smb_search_data *d; + unsigned int count; + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + + smb2_deltree(tree, dname); + + status = torture_smb2_testdir(tree, dname, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed"); + + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = 0; + io.in.fname = fname; + + status = smb2_create(tree, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + + ZERO_STRUCT(sfinfo); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_HIDDEN; + sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sfinfo.generic.in.file.handle = io.out.file.handle; + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_filefailed"); + + smb2_util_close(tree, io.out.file.handle); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "file"; + f.in.continue_flags = SMB2_CONTINUE_FLAG_RESTART; + f.in.max_response_size = 0x1000; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + status = smb2_find_level(tree, tree, &f, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + + smb2_util_close(tree, h); + ZERO_STRUCT(h); + + torture_assert_goto(tctx, + d->both_directory_info.attrib & FILE_ATTRIBUTE_HIDDEN, + ret, done, + "FILE_ATTRIBUTE_HIDDEN is not set\n"); + +done: + if (!smb2_util_handle_empty(h)) { + smb2_util_close(tree, h); + } + smb2_deltree(tree, dname); + return ret; +} diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c new file mode 100644 index 0000000..9cd4d88 --- /dev/null +++ b/source4/torture/smb2/durable_open.c @@ -0,0 +1,2871 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 durable opens + + Copyright (C) Stefan Metzmacher 2008 + Copyright (C) Michael Adam 2011-2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should be 0x%llx\n", \ + __location__, #v, (unsigned long long)v, (unsigned long long)correct); \ + ret = false; \ + }} while (0) + +#define CHECK_NOT_VAL(v, incorrect) do { \ + if ((v) == (incorrect)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should not be 0x%llx\n", \ + __location__, #v, (unsigned long long)v, (unsigned long long)incorrect); \ + ret = false; \ + }} while (0) + +#define CHECK_NOT_NULL(p) do { \ + if ((p) == NULL) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): %s is NULL but it should not be.\n", \ + __location__, #p); \ + ret = false; \ + }} while (0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ + nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_CREATED(__io, __created, __attribute) \ + do { \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ + CHECK_VAL((__io)->out.size, 0); \ + CHECK_VAL((__io)->out.file_attr, (__attribute)); \ + CHECK_VAL((__io)->out.reserved2, 0); \ + } while(0) + +#define CHECK_CREATED_SIZE(__io, __created, __attribute, __alloc_size, __size) \ + do { \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ + CHECK_VAL((__io)->out.alloc_size, (__alloc_size)); \ + CHECK_VAL((__io)->out.size, (__size)); \ + CHECK_VAL((__io)->out.file_attr, (__attribute)); \ + CHECK_VAL((__io)->out.reserved2, 0); \ + } while(0) + + + +/** + * basic durable_open test. + * durable state should only be granted when requested + * along with a batch oplock or a handle lease. + * + * This test tests durable open with all possible oplock types. + */ + +struct durable_open_vs_oplock { + const char *level; + const char *share_mode; + bool expected; +}; + +#define NUM_OPLOCK_TYPES 4 +#define NUM_SHARE_MODES 8 +#define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES ) +static struct durable_open_vs_oplock durable_open_vs_oplock_table[NUM_OPLOCK_OPEN_TESTS] = +{ + { "", "", false }, + { "", "R", false }, + { "", "W", false }, + { "", "D", false }, + { "", "RD", false }, + { "", "RW", false }, + { "", "WD", false }, + { "", "RWD", false }, + + { "s", "", false }, + { "s", "R", false }, + { "s", "W", false }, + { "s", "D", false }, + { "s", "RD", false }, + { "s", "RW", false }, + { "s", "WD", false }, + { "s", "RWD", false }, + + { "x", "", false }, + { "x", "R", false }, + { "x", "W", false }, + { "x", "D", false }, + { "x", "RD", false }, + { "x", "RW", false }, + { "x", "WD", false }, + { "x", "RWD", false }, + + { "b", "", true }, + { "b", "R", true }, + { "b", "W", true }, + { "b", "D", true }, + { "b", "RD", true }, + { "b", "RW", true }, + { "b", "WD", true }, + { "b", "RWD", true }, +}; + +static bool test_one_durable_open_open_oplock(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + struct durable_open_vs_oplock test) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + bool ret = true; + struct smb2_create io; + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(test.share_mode), + smb2_util_oplock_level(test.level)); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, test.expected); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(test.level)); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_durable_open_open_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + bool ret = true; + int i; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_open_oplock_%s.dat", generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + /* test various oplock levels with durable open */ + + for (i = 0; i < NUM_OPLOCK_OPEN_TESTS; i++) { + ret = test_one_durable_open_open_oplock(tctx, + tree, + fname, + durable_open_vs_oplock_table[i]); + if (ret == false) { + goto done; + } + } + +done: + smb2_util_unlink(tree, fname); + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * basic durable_open test. + * durable state should only be granted when requested + * along with a batch oplock or a handle lease. + * + * This test tests durable open with all valid lease types. + */ + +struct durable_open_vs_lease { + const char *type; + const char *share_mode; + bool expected; +}; + +#define NUM_LEASE_TYPES 5 +#define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES ) +static struct durable_open_vs_lease durable_open_vs_lease_table[NUM_LEASE_OPEN_TESTS] = +{ + { "", "", false }, + { "", "R", false }, + { "", "W", false }, + { "", "D", false }, + { "", "RW", false }, + { "", "RD", false }, + { "", "WD", false }, + { "", "RWD", false }, + + { "R", "", false }, + { "R", "R", false }, + { "R", "W", false }, + { "R", "D", false }, + { "R", "RW", false }, + { "R", "RD", false }, + { "R", "DW", false }, + { "R", "RWD", false }, + + { "RW", "", false }, + { "RW", "R", false }, + { "RW", "W", false }, + { "RW", "D", false }, + { "RW", "RW", false }, + { "RW", "RD", false }, + { "RW", "WD", false }, + { "RW", "RWD", false }, + + { "RH", "", true }, + { "RH", "R", true }, + { "RH", "W", true }, + { "RH", "D", true }, + { "RH", "RW", true }, + { "RH", "RD", true }, + { "RH", "WD", true }, + { "RH", "RWD", true }, + + { "RHW", "", true }, + { "RHW", "R", true }, + { "RHW", "W", true }, + { "RHW", "D", true }, + { "RHW", "RW", true }, + { "RHW", "RD", true }, + { "RHW", "WD", true }, + { "RHW", "RWD", true }, +}; + +static bool test_one_durable_open_open_lease(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + struct durable_open_vs_lease test) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + bool ret = true; + struct smb2_create io; + struct smb2_lease ls; + uint64_t lease; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + lease = random(); + + smb2_lease_create_share(&io, &ls, false /* dir */, fname, + smb2_util_share_access(test.share_mode), + lease, + smb2_util_lease_state(test.type)); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, test.expected); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state(test.type)); +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_durable_open_open_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + bool ret = true; + int i; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_open_lease_%s.dat", generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + + /* test various oplock levels with durable open */ + + for (i = 0; i < NUM_LEASE_OPEN_TESTS; i++) { + ret = test_one_durable_open_open_lease(tctx, + tree, + fname, + durable_open_vs_lease_table[i]); + if (ret == false) { + goto done; + } + } + +done: + smb2_util_unlink(tree, fname); + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * basic test for doing a durable open + * and do a durable reopen on the same connection + * while the first open is still active (fails) + */ +static bool test_durable_open_reopen1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io1, io2; + bool ret = true; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io1.out.file.handle; + h = &_h; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, true); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + + /* try a durable reconnect while the file is still open */ + ZERO_STRUCT(io2); + io2.in.fname = fname; + io2.in.durable_handle = h; + + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * Basic test for doing a durable open + * and do a session reconnect while the first + * session is still active and the handle is + * still open in the client. + * This closes the original session and a + * durable reconnect on the new session succeeds. + */ +static bool test_durable_open_reopen1a(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + bool ret = true; + struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; + uint64_t previous_session_id; + struct smbcli_options options; + struct GUID orig_client_guid; + + options = tree->session->transport->options; + orig_client_guid = options.client_guid; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen1a_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + /* + * a session reconnect on a second tcp connection + */ + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* for oplocks, the client guid can be different: */ + options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "could not reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + + TALLOC_FREE(tree); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + + /* + * a session reconnect on a second tcp connection + */ + + previous_session_id = smb2cli_session_current_id(tree2->session->smbXcli); + + /* the original client_guid works just the same */ + options.client_guid = orig_client_guid; + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "could not reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + +done: + if (tree == NULL) { + tree = tree2; + } + + if (tree == NULL) { + tree = tree3; + } + + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + h = NULL; + } + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * lease variant of reopen1a + * + * Basic test for doing a durable open and doing a session + * reconnect while the first session is still active and the + * handle is still open in the client. + * This closes the original session and a durable reconnect on + * the new session succeeds depending on the client guid: + * + * Durable reconnect on a session with a different client guid fails. + * Durable reconnect on a session with the original client guid succeeds. + */ +bool test_durable_open_reopen1a_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; + uint64_t previous_session_id; + struct smbcli_options options; + struct GUID orig_client_guid; + + options = tree->session->transport->options; + orig_client_guid = options.client_guid; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen1a_lease_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_create(&io, &ls, false /* dir */, fname, + lease_key, smb2_util_lease_state("RWH")); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* + * a session reconnect on a second tcp connection + * with a different client_guid does not allow + * the durable reconnect. + */ + + options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree); + + + /* + * but a durable reconnect on the new session with the wrong + * client guid fails + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * now a session reconnect on a second tcp connection + * with original client_guid allows the durable reconnect. + */ + + options.client_guid = orig_client_guid; + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + * In this case, a durable reconnect attempt with the + * correct client_guid yields a different error code. + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + io.in.lease_request = &ls; + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); /* no dh response context... */ + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + +done: + if (tree == NULL) { + tree = tree2; + } + + if (tree == NULL) { + tree = tree3; + } + + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + + +/** + * basic test for doing a durable open + * tcp disconnect, reconnect, do a durable reopen (succeeds) + */ +static bool test_durable_open_reopen2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + bool ret = true; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + /* disconnect, leaving the durable in place */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + /* the path name is ignored by the server */ + io.in.fname = fname; + io.in.durable_handle = h; /* durable v1 reconnect request */ + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + + /* disconnect again, leaving the durable in place */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * show that the filename and many other fields + * are ignored. only the reconnect request blob + * is important. + */ + ZERO_STRUCT(io); + /* the path name is ignored by the server */ + io.in.security_flags = 0x78; + io.in.oplock_level = 0x78; + io.in.impersonation_level = 0x12345678; + io.in.create_flags = 0x12345678; + io.in.reserved = 0x12345678; + io.in.desired_access = 0x12345678; + io.in.file_attributes = 0x12345678; + io.in.share_access = 0x12345678; + io.in.create_disposition = 0x12345678; + io.in.create_options = 0x12345678; + io.in.fname = "__non_existing_fname__"; + io.in.durable_handle = h; /* durable v1 reconnect request */ + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + + /* disconnect, leaving the durable in place */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * show that an additionally specified durable v1 request + * is ignored by the server. + * See MS-SMB2, 3.3.5.9.7 + * Handling the SMB2_CREATE_DURABLE_HANDLE_RECONNECT Create Context + */ + ZERO_STRUCT(io); + /* the path name is ignored by the server */ + io.in.fname = fname; + io.in.durable_handle = h; /* durable v1 reconnect request */ + io.in.durable_open = true; /* durable v1 handle request */ + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * lease variant of reopen2 + * basic test for doing a durable open + * tcp disconnect, reconnect, do a durable reopen (succeeds) + */ +static bool test_durable_open_reopen2_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_create(&io, &ls, false /* dir */, fname, lease_key, + smb2_util_lease_state("RWH")); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + + /* a few failure tests: */ + + /* + * several attempts without lease attached: + * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND + * irrespective of file name provided + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_handle = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * attempt with lease provided, but + * with a changed lease key. => fails + */ + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open = false; + io.in.durable_handle = h; + io.in.lease_request = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + /* a wrong lease key lets the request fail */ + ls.lease_key.data[0]++; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* restore the correct lease key */ + ls.lease_key.data[0]--; + + /* + * this last failing attempt is almost correct: + * only problem is: we use the wrong filename... + * Note that this gives INVALID_PARAMETER. + * This is different from oplocks! + */ + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_open = false; + io.in.durable_handle = h; + io.in.lease_request = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* + * Now for a succeeding reconnect: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open = false; + io.in.durable_handle = h; + io.in.lease_request = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + + /* disconnect one more time */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * demonstrate that various parameters are ignored + * in the reconnect + */ + + ZERO_STRUCT(io); + /* + * These are completely ignored by the server + */ + io.in.security_flags = 0x78; + io.in.oplock_level = 0x78; + io.in.impersonation_level = 0x12345678; + io.in.create_flags = 0x12345678; + io.in.reserved = 0x12345678; + io.in.desired_access = 0x12345678; + io.in.file_attributes = 0x12345678; + io.in.share_access = 0x12345678; + io.in.create_disposition = 0x12345678; + io.in.create_options = 0x12345678; + + /* + * only these are checked: + * - io.in.fname + * - io.in.durable_handle, + * - io.in.lease_request->lease_key + */ + + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.lease_request = &ls; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + _h = io.out.file.handle; + h = &_h; + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * lease v2 variant of reopen2 + * basic test for doing a durable open + * tcp disconnect, reconnect, do a durable reopen (succeeds) + */ +static bool test_durable_open_reopen2_lease_v2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response_v2.lease_flags, 0); + CHECK_VAL(io.out.lease_response_v2.lease_duration, 0); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* a few failure tests: */ + + /* + * several attempts without lease attached: + * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND + * irrespective of file name provided + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_handle = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * attempt with lease provided, but + * with a changed lease key. => fails + */ + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open = false; + io.in.durable_handle = h; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + /* a wrong lease key lets the request fail */ + ls.lease_key.data[0]++; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* restore the correct lease key */ + ls.lease_key.data[0]--; + + /* + * this last failing attempt is almost correct: + * only problem is: we use the wrong filename... + * Note that this gives INVALID_PARAMETER. + * This is different from oplocks! + */ + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_open = false; + io.in.durable_handle = h; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* + * Now for a succeeding reconnect: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open = false; + io.in.durable_handle = h; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response_v2.lease_flags, 0); + CHECK_VAL(io.out.lease_response_v2.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + + /* disconnect one more time */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * demonstrate that various parameters are ignored + * in the reconnect + */ + + ZERO_STRUCT(io); + /* + * These are completely ignored by the server + */ + io.in.security_flags = 0x78; + io.in.oplock_level = 0x78; + io.in.impersonation_level = 0x12345678; + io.in.create_flags = 0x12345678; + io.in.reserved = 0x12345678; + io.in.desired_access = 0x12345678; + io.in.file_attributes = 0x12345678; + io.in.share_access = 0x12345678; + io.in.create_disposition = 0x12345678; + io.in.create_options = 0x12345678; + + /* + * only these are checked: + * - io.in.fname + * - io.in.durable_handle, + * - io.in.lease_request->lease_key + */ + + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.lease_request_v2 = &ls; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response_v2.lease_flags, 0); + CHECK_VAL(io.out.lease_response_v2.lease_duration, 0); + + _h = io.out.file.handle; + h = &_h; + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * basic test for doing a durable open + * tcp disconnect, reconnect with a session reconnect and + * do a durable reopen (succeeds) + */ +static bool test_durable_open_reopen2a(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io1, io2; + uint64_t previous_session_id; + bool ret = true; + struct smbcli_options options; + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io1.out.file.handle; + h = &_h; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, true); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + + /* disconnect, reconnect and then do durable reopen */ + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree)) + { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io2); + io2.in.fname = fname; + io2.in.durable_handle = h; + h = NULL; + + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + _h = io2.out.file.handle; + h = &_h; + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + + +/** + * basic test for doing a durable open: + * tdis, new tcon, try durable reopen (fails) + */ +static bool test_durable_open_reopen3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io1, io2; + bool ret = true; + struct smb2_tree *tree2; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen3_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io1.out.file.handle; + h = &_h; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, true); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + + /* disconnect, reconnect and then do durable reopen */ + status = smb2_tdis(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!torture_smb2_tree_connect(tctx, tree->session, mem_ctx, &tree2)) { + torture_warning(tctx, "couldn't reconnect to share, bailing\n"); + ret = false; + goto done; + } + + + ZERO_STRUCT(io2); + io2.in.fname = fname; + io2.in.durable_handle = h; + + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree2, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * basic test for doing a durable open: + * logoff, create a new session, do a durable reopen (succeeds) + */ +static bool test_durable_open_reopen4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io1, io2; + bool ret = true; + struct smb2_transport *transport; + struct smb2_session *session2; + struct smb2_tree *tree2; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_reopen4_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io1.out.file.handle; + h = &_h; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, true); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + + /* + * do a session logoff, establish a new session and tree + * connect on the same transport, and try a durable reopen + */ + transport = tree->session->transport; + status = smb2_logoff(tree->session); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!torture_smb2_session_setup(tctx, transport, + 0, /* previous_session_id */ + mem_ctx, &session2)) + { + torture_warning(tctx, "session setup failed.\n"); + ret = false; + goto done; + } + + /* + * the session setup has talloc-stolen the transport, + * so we can safely free the old tree+session for clarity + */ + TALLOC_FREE(tree); + + if (!torture_smb2_tree_connect(tctx, session2, mem_ctx, &tree2)) { + torture_warning(tctx, "tree connect failed.\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io2); + io2.in.fname = fname; + io2.in.durable_handle = h; + h = NULL; + + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + + _h = io2.out.file.handle; + h = &_h; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree2, *h); + } + + smb2_util_unlink(tree2, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_durable_open_delete_on_close1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io1, io2; + bool ret = true; + uint8_t b = 0; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_delete_on_close1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.durable_open = true; + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io1.out.file.handle; + h = &_h; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, true); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + + status = smb2_util_write(tree, *h, &b, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + + /* disconnect, leaving the durable handle in place */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "could not reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * Open the file on the new connection again + * and check that it has been newly created, + * i.e. delete on close was effective on the disconnected handle. + * Also check that the file is really empty, + * the previously written byte gone. + */ + smb2_oplock_create_share(&io2, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io2.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io2.out.file.handle; + h = &_h; + CHECK_CREATED_SIZE(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE, 0, 0); + CHECK_VAL(io2.out.durable_open, false); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + + +static bool test_durable_open_delete_on_close2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + bool ret = true; + uint8_t b = 0; + uint64_t previous_session_id; + uint64_t alloc_size_step; + struct smbcli_options options; + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_delete_on_close2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + io.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + status = smb2_util_write(tree, *h, &b, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* disconnect, leaving the durable handle in place */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree)) + { + torture_warning(tctx, "could not reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + alloc_size_step = io.out.alloc_size; + CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE, alloc_size_step, 1); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + /* close the file, thereby deleting it */ + smb2_util_close(tree, *h); + status = smb2_logoff(tree->session); + TALLOC_FREE(tree); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "could not reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * Open the file on the new connection again + * and check that it has been newly created, + * i.e. delete on close was effective on the reconnected handle. + * Also check that the file is really empty, + * the previously written byte gone. + */ + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + io.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED_SIZE(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE, 0, 0); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/* + basic testing of SMB2 durable opens + regarding the position information on the handle +*/ +static bool test_durable_open_file_position(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle h; + struct smb2_create io; + NTSTATUS status; + const char *fname = "durable_open_position.dat"; + union smb_fileinfo qfinfo; + union smb_setfileinfo sfinfo; + bool ret = true; + uint64_t pos; + uint64_t previous_session_id; + struct smbcli_options options; + + options = tree->session->transport->options; + + smb2_util_unlink(tree, fname); + + smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_BATCH); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* TODO: check extra blob content */ + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = h; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(qfinfo.position_information.out.position, 0); + pos = qfinfo.position_information.out.position; + torture_comment(tctx, "position: %llu\n", + (unsigned long long)pos); + + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION; + sfinfo.generic.in.file.handle = h; + sfinfo.position_information.in.position = 0x1000; + status = smb2_setinfo_file(tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = h; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(qfinfo.position_information.out.position, 0x1000); + pos = qfinfo.position_information.out.position; + torture_comment(tctx, "position: %llu\n", + (unsigned long long)pos); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* tcp disconnect */ + talloc_free(tree); + tree = NULL; + + /* do a session reconnect */ + if (!torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree)) + { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = h; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = &h; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + CHECK_VAL(io.out.reserved, 0x00); + CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_VAL(io.out.alloc_size, 0); + CHECK_VAL(io.out.size, 0); + CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.reserved2, 0); + + h = io.out.file.handle; + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = h; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(qfinfo.position_information.out.position, 0x1000); + pos = qfinfo.position_information.out.position; + torture_comment(tctx, "position: %llu\n", + (unsigned long long)pos); + + smb2_util_close(tree, h); + + talloc_free(mem_ctx); + + smb2_util_unlink(tree, fname); + +done: + talloc_free(tree); + + return ret; +} + +/* + Open, disconnect, oplock break, reconnect. +*/ +static bool test_durable_open_oplock(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1, io2; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + char fname[256]; + bool ret = true; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_oplock_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree1, fname); + + /* Create with batch oplock */ + smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH); + io1.in.durable_open = true; + + io2 = io1; + io2.in.create_disposition = NTCREATEX_DISP_OPEN; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, true); + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* Disconnect after getting the batch */ + talloc_free(tree1); + tree1 = NULL; + + /* + * Windows7 (build 7000) will break a batch oplock immediately if the + * original client is gone. (ZML: This seems like a bug. It should give + * some time for the client to reconnect!) + */ + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.durable_open, true); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* What if tree1 tries to come back and reclaim? */ + if (!torture_smb2_connection(tctx, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io1); + io1.in.fname = fname; + io1.in.durable_handle = &h1; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + done: + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + + talloc_free(tree1); + talloc_free(tree2); + + return ret; +} + +/* + Open, disconnect, lease break, reconnect. +*/ +static bool test_durable_open_lease(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1, io2; + struct smb2_lease ls1, ls2; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease1, lease2; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease1 = random(); + lease2 = random(); + snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree1, fname); + + /* Create with lease */ + smb2_lease_create(&io1, &ls1, false /* dir */, fname, + lease1, smb2_util_lease_state("RHW")); + io1.in.durable_open = true; + + smb2_lease_create(&io2, &ls2, false /* dir */, fname, + lease2, smb2_util_lease_state("RHW")); + io2.in.durable_open = true; + io2.in.create_disposition = NTCREATEX_DISP_OPEN; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_VAL(io1.out.durable_open, true); + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease1); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease1); + CHECK_VAL(io1.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + + /* Disconnect after getting the lease */ + talloc_free(tree1); + tree1 = NULL; + + /* + * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?) + * even if the original client is gone. (ZML: This seems like a bug. It + * should give some time for the client to reconnect! And why RH?) + * + * obnox: Current windows 7 and w2k8r2 grant RHW instead of RH. + * Test is adapted accordingly. + */ + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_VAL(io2.out.durable_open, true); + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io2.out.lease_response.lease_key.data[0], lease2); + CHECK_VAL(io2.out.lease_response.lease_key.data[1], ~lease2); + CHECK_VAL(io2.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + + /* What if tree1 tries to come back and reclaim? */ + if (!torture_smb2_connection(tctx, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io1); + io1.in.fname = fname; + io1.in.durable_handle = &h1; + io1.in.lease_request = &ls1; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + done: + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + + talloc_free(tree1); + talloc_free(tree2); + + return ret; +} + +static bool test_durable_open_lock_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_handle h = {{0}}; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + NTSTATUS status; + char fname[256]; + bool ret = true; + + /* + */ + snprintf(fname, 256, "durable_open_oplock_lock_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree, fname); + + /* Create with oplock */ + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Disconnect/Reconnect. */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = &h; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + talloc_free(tree); + + return ret; +} + +/* + Open, take BRL, disconnect, reconnect. +*/ +static bool test_durable_open_lock_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h = {{0}}; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + uint32_t caps; + struct smbcli_options options; + + options = tree->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease = random(); + snprintf(fname, 256, "durable_open_lease_lock_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree, fname); + + /* Create with lease */ + + smb2_lease_create(&io, &ls, false /* dir */, fname, lease, + smb2_util_lease_state("RWH")); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease); + CHECK_VAL(io.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Disconnect/Reconnect. */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = &h; + io.in.lease_request = &ls; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + talloc_free(tree); + + return ret; +} + +/** + * Open with a RH lease, disconnect, open in another tree, reconnect. + * + * This test actually demonstrates a minimum level of respect for the durable + * open in the face of another open. As long as this test shows an inability to + * reconnect after an open, the oplock/lease tests above will certainly + * demonstrate an error on reconnect. + */ +static bool test_durable_open_open2_lease(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1, io2; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + uint32_t caps; + struct smbcli_options options; + + options = tree1->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease = random(); + snprintf(fname, 256, "durable_open_open2_lease_%s.dat", + generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree1, fname); + + /* Create with lease */ + smb2_lease_create_share(&io1, &ls, false /* dir */, fname, + smb2_util_share_access(""), + lease, + smb2_util_lease_state("RH")); + io1.in.durable_open = true; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_VAL(io1.out.durable_open, true); + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease); + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("RH")); + + /* Disconnect */ + talloc_free(tree1); + tree1 = NULL; + + /* Open the file in tree2 */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + + /* Reconnect */ + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io1); + io1.in.fname = fname; + io1.in.durable_handle = &h1; + io1.in.lease_request = &ls; + + /* + * Windows7 (build 7000) will give away an open immediately if the + * original client is gone. (ZML: This seems like a bug. It should give + * some time for the client to reconnect!) + */ + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h1 = io1.out.file.handle; + + done: + if (tree1 != NULL){ + smb2_util_close(tree1, h1); + smb2_util_unlink(tree1, fname); + talloc_free(tree1); + } + + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + talloc_free(tree2); + + return ret; +} + +/** + * Open with a batch oplock, disconnect, open in another tree, reconnect. + * + * This test actually demonstrates a minimum level of respect for the durable + * open in the face of another open. As long as this test shows an inability to + * reconnect after an open, the oplock/lease tests above will certainly + * demonstrate an error on reconnect. + */ +static bool test_durable_open_open2_oplock(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1, io2; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + char fname[256]; + bool ret = true; + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + snprintf(fname, 256, "durable_open_open2_oplock_%s.dat", + generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree1, fname); + + /* Create with batch oplock */ + smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH); + io1.in.durable_open = true; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, true); + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* Disconnect */ + talloc_free(tree1); + tree1 = NULL; + + /* Open the file in tree2 */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + /* Reconnect */ + if (!torture_smb2_connection(tctx, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io1); + io1.in.fname = fname; + io1.in.durable_handle = &h1; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h1 = io1.out.file.handle; + + done: + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + if (tree1 != NULL) { + smb2_util_close(tree1, h1); + smb2_util_unlink(tree1, fname); + } + + talloc_free(tree1); + talloc_free(tree2); + + return ret; +} + +/** + * test behaviour with initial allocation size + */ +static bool test_durable_open_alloc_size(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + bool ret = true; + uint64_t previous_session_id; + uint64_t alloc_size_step; + uint64_t initial_alloc_size = 0x1000; + const uint8_t *b = NULL; + struct smbcli_options options; + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_alloc_size_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + io.in.alloc_size = initial_alloc_size; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_NOT_VAL(io.out.alloc_size, 0); + alloc_size_step = io.out.alloc_size; + CHECK_CREATED_SIZE(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE, + alloc_size_step, 0); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + /* prepare buffer */ + b = talloc_zero_size(mem_ctx, alloc_size_step); + CHECK_NOT_NULL(b); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* disconnect, reconnect and then do durable reopen */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree)) + { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE, + alloc_size_step, 0); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* write one byte */ + status = smb2_util_write(tree, *h, b, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + + /* disconnect, reconnect and then do durable reopen */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree)) + { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE, + alloc_size_step, 1); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* write more byte than initial allocation size */ + status = smb2_util_write(tree, *h, b, 1, alloc_size_step); + + /* disconnect, reconnect and then do durable reopen */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree)) + { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE, + alloc_size_step * 2, alloc_size_step + 1); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * test behaviour when a disconnect happens while creating a read-only file + */ +static bool test_durable_open_read_only(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + bool ret = true; + uint64_t previous_session_id; + const uint8_t b = 0; + uint64_t alloc_size = 0; + struct smbcli_options options; + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_initial_alloc_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + io.in.file_attributes = FILE_ATTRIBUTE_READONLY; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* write one byte */ + status = smb2_util_write(tree, *h, &b, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + + /* disconnect, reconnect and then do durable reopen */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree)) + { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + alloc_size = io.out.alloc_size; + CHECK_CREATED_SIZE(&io, EXISTED, + FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE, + alloc_size, 1); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + + /* write one byte */ + status = smb2_util_write(tree, *h, &b, 1, 1); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + if (h != NULL) { + union smb_setfileinfo sfinfo; + + ZERO_STRUCT(sfinfo); + sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION; + sfinfo.basic_info.in.file.handle = *h; + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL; + smb2_setinfo_file(tree, &sfinfo); + + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * durable open with oplock, disconnect, exit + */ +static bool test_durable_open_oplock_disconnect(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + NTSTATUS status; + char fname[256]; + bool ret = true; + + snprintf(fname, 256, "durable_open_oplock_disconnect_%s.dat", + generate_random_str(mem_ctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_BATCH); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + _h = io.out.file.handle; + h = &_h; + + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* disconnect */ + talloc_free(tree); + tree = NULL; + +done: + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_util_unlink(tree, fname); + } + talloc_free(mem_ctx); + return ret; +} + +/** + * durable stat open with lease. + */ +static bool test_durable_open_stat_open(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_lease ls; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + + snprintf(fname, 256, "durable_open_stat_open_%s.dat", + generate_random_str(mem_ctx, 8)); + + /* Ensure file doesn't exist. */ + smb2_util_unlink(tree, fname); + + /* Create a normal file. */ + smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_NONE); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + /* Close. */ + smb2_util_close(tree, *h); + h = NULL; + + /* Now try a leased, durable handle stat open. */ + lease = random(); + /* Create with lease */ + smb2_lease_create(&io, + &ls, + false /* dir */, + fname, + lease, + smb2_util_lease_state("RH")); + io.in.durable_open = true; + io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, true); + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +struct torture_suite *torture_smb2_durable_open_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "durable-open"); + + torture_suite_add_1smb2_test(suite, "open-oplock", test_durable_open_open_oplock); + torture_suite_add_1smb2_test(suite, "open-lease", test_durable_open_open_lease); + torture_suite_add_1smb2_test(suite, "reopen1", test_durable_open_reopen1); + torture_suite_add_1smb2_test(suite, "reopen1a", test_durable_open_reopen1a); + torture_suite_add_1smb2_test(suite, "reopen1a-lease", test_durable_open_reopen1a_lease); + torture_suite_add_1smb2_test(suite, "reopen2", test_durable_open_reopen2); + torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_open_reopen2_lease); + torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_open_reopen2_lease_v2); + torture_suite_add_1smb2_test(suite, "reopen2a", test_durable_open_reopen2a); + torture_suite_add_1smb2_test(suite, "reopen3", test_durable_open_reopen3); + torture_suite_add_1smb2_test(suite, "reopen4", test_durable_open_reopen4); + torture_suite_add_1smb2_test(suite, "delete_on_close1", + test_durable_open_delete_on_close1); + torture_suite_add_1smb2_test(suite, "delete_on_close2", + test_durable_open_delete_on_close2); + torture_suite_add_1smb2_test(suite, "file-position", + test_durable_open_file_position); + torture_suite_add_2smb2_test(suite, "oplock", test_durable_open_oplock); + torture_suite_add_2smb2_test(suite, "lease", test_durable_open_lease); + torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_open_lock_oplock); + torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_open_lock_lease); + torture_suite_add_2smb2_test(suite, "open2-lease", + test_durable_open_open2_lease); + torture_suite_add_2smb2_test(suite, "open2-oplock", + test_durable_open_open2_oplock); + torture_suite_add_1smb2_test(suite, "alloc-size", + test_durable_open_alloc_size); + torture_suite_add_1smb2_test(suite, "read-only", + test_durable_open_read_only); + torture_suite_add_1smb2_test(suite, "stat-open", + test_durable_open_stat_open); + + suite->description = talloc_strdup(suite, "SMB2-DURABLE-OPEN tests"); + + return suite; +} + +struct torture_suite *torture_smb2_durable_open_disconnect_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, + "durable-open-disconnect"); + + torture_suite_add_1smb2_test(suite, "open-oplock-disconnect", + test_durable_open_oplock_disconnect); + + suite->description = talloc_strdup(suite, + "SMB2-DURABLE-OPEN-DISCONNECT tests"); + + return suite; +} diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c new file mode 100644 index 0000000..9b9af11 --- /dev/null +++ b/source4/torture/smb2/durable_v2_open.c @@ -0,0 +1,2371 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 version two of durable opens + + Copyright (C) Michael Adam 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "librpc/ndr/libndr.h" + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ + nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_CREATED(__io, __created, __attribute) \ + do { \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ + CHECK_VAL((__io)->out.size, 0); \ + CHECK_VAL((__io)->out.file_attr, (__attribute)); \ + CHECK_VAL((__io)->out.reserved2, 0); \ + } while(0) + +static struct { + int count; + struct smb2_close cl; +} break_info; + +static void torture_oplock_close_callback(struct smb2_request *req) +{ + smb2_close_recv(req, &break_info.cl); +} + +/* A general oplock break notification handler. This should be used when a + * test expects to break from batch or exclusive to a lower level. */ +static bool torture_oplock_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_tree *tree = private_data; + struct smb2_request *req; + + break_info.count++; + + ZERO_STRUCT(break_info.cl); + break_info.cl.in.file.handle = *handle; + + req = smb2_close_send(tree, &break_info.cl); + req->async.fn = torture_oplock_close_callback; + req->async.private_data = NULL; + return true; +} + +/** + * testing various create blob combinations. + */ +bool test_durable_v2_open_create_blob(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + struct smbcli_options options; + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_create_blob_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + + /* disconnect */ + TALLOC_FREE(tree); + + /* create a new session (same client_guid) */ + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * check invalid combinations of durable handle + * request and reconnect blobs + * See MS-SMB2: 3.3.5.9.12 + * Handling the SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2 Create Context + */ + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; /* durable v2 reconnect request */ + io.in.durable_open = true; /* durable v1 handle request */ + io.in.create_guid = create_guid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; /* durable v1 reconnect request */ + io.in.durable_open_v2 = true; /* durable v2 handle request */ + io.in.create_guid = create_guid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; /* durable v1 reconnect request */ + io.in.durable_handle_v2 = h; /* durable v2 reconnect request */ + io.in.create_guid = create_guid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; /* durable v2 reconnect request */ + io.in.durable_open_v2 = true; /* durable v2 handle request */ + io.in.create_guid = create_guid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + + +/** + * basic durable_open test. + * durable state should only be granted when requested + * along with a batch oplock or a handle lease. + * + * This test tests durable open with all possible oplock types. + */ + +struct durable_open_vs_oplock { + const char *level; + const char *share_mode; + bool durable; + bool persistent; +}; + +#define NUM_OPLOCK_TYPES 4 +#define NUM_SHARE_MODES 8 +#define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES ) +static struct durable_open_vs_oplock durable_open_vs_oplock_table[NUM_OPLOCK_OPEN_TESTS] = +{ + { "", "", false, false }, + { "", "R", false, false }, + { "", "W", false, false }, + { "", "D", false, false }, + { "", "RD", false, false }, + { "", "RW", false, false }, + { "", "WD", false, false }, + { "", "RWD", false, false }, + + { "s", "", false, false }, + { "s", "R", false, false }, + { "s", "W", false, false }, + { "s", "D", false, false }, + { "s", "RD", false, false }, + { "s", "RW", false, false }, + { "s", "WD", false, false }, + { "s", "RWD", false, false }, + + { "x", "", false, false }, + { "x", "R", false, false }, + { "x", "W", false, false }, + { "x", "D", false, false }, + { "x", "RD", false, false }, + { "x", "RW", false, false }, + { "x", "WD", false, false }, + { "x", "RWD", false, false }, + + { "b", "", true, false }, + { "b", "R", true, false }, + { "b", "W", true, false }, + { "b", "D", true, false }, + { "b", "RD", true, false }, + { "b", "RW", true, false }, + { "b", "WD", true, false }, + { "b", "RWD", true, false }, +}; + +static bool test_one_durable_v2_open_oplock(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + bool request_persistent, + struct durable_open_vs_oplock test) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + bool ret = true; + struct smb2_create io; + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(test.share_mode), + smb2_util_oplock_level(test.level)); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = request_persistent; + io.in.create_guid = GUID_random(); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, test.durable); + CHECK_VAL(io.out.persistent_open, test.persistent); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(test.level)); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_durable_v2_open_oplock_table(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + bool request_persistent, + struct durable_open_vs_oplock *table, + uint8_t num_tests) +{ + bool ret = true; + uint8_t i; + + smb2_util_unlink(tree, fname); + + for (i = 0; i < num_tests; i++) { + ret = test_one_durable_v2_open_oplock(tctx, + tree, + fname, + request_persistent, + table[i]); + if (ret == false) { + goto done; + } + } + +done: + smb2_util_unlink(tree, fname); + + return ret; +} + +bool test_durable_v2_open_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + bool ret; + char fname[256]; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_oplock_%s.dat", + generate_random_str(tctx, 8)); + + ret = test_durable_v2_open_oplock_table(tctx, tree, fname, + false, /* request_persistent */ + durable_open_vs_oplock_table, + NUM_OPLOCK_OPEN_TESTS); + + talloc_free(tree); + + return ret; +} + +/** + * basic durable handle open test. + * persistent state should only be granted when requested + * along with a batch oplock or a handle lease. + * + * This test tests persistent open with all valid lease types. + */ + +struct durable_open_vs_lease { + const char *type; + const char *share_mode; + bool durable; + bool persistent; +}; + +#define NUM_LEASE_TYPES 5 +#define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES ) +static struct durable_open_vs_lease durable_open_vs_lease_table[NUM_LEASE_OPEN_TESTS] = +{ + { "", "", false, false }, + { "", "R", false, false }, + { "", "W", false, false }, + { "", "D", false, false }, + { "", "RW", false, false }, + { "", "RD", false, false }, + { "", "WD", false, false }, + { "", "RWD", false, false }, + + { "R", "", false, false }, + { "R", "R", false, false }, + { "R", "W", false, false }, + { "R", "D", false, false }, + { "R", "RW", false, false }, + { "R", "RD", false, false }, + { "R", "DW", false, false }, + { "R", "RWD", false, false }, + + { "RW", "", false, false }, + { "RW", "R", false, false }, + { "RW", "W", false, false }, + { "RW", "D", false, false }, + { "RW", "RW", false, false }, + { "RW", "RD", false, false }, + { "RW", "WD", false, false }, + { "RW", "RWD", false, false }, + + { "RH", "", true, false }, + { "RH", "R", true, false }, + { "RH", "W", true, false }, + { "RH", "D", true, false }, + { "RH", "RW", true, false }, + { "RH", "RD", true, false }, + { "RH", "WD", true, false }, + { "RH", "RWD", true, false }, + + { "RHW", "", true, false }, + { "RHW", "R", true, false }, + { "RHW", "W", true, false }, + { "RHW", "D", true, false }, + { "RHW", "RW", true, false }, + { "RHW", "RD", true, false }, + { "RHW", "WD", true, false }, + { "RHW", "RWD", true, false }, +}; + +static bool test_one_durable_v2_open_lease(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + bool request_persistent, + struct durable_open_vs_lease test) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + bool ret = true; + struct smb2_create io; + struct smb2_lease ls; + uint64_t lease; + + smb2_util_unlink(tree, fname); + + lease = random(); + + smb2_lease_create_share(&io, &ls, false /* dir */, fname, + smb2_util_share_access(test.share_mode), + lease, + smb2_util_lease_state(test.type)); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = request_persistent; + io.in.create_guid = GUID_random(); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, test.durable); + CHECK_VAL(io.out.persistent_open, test.persistent); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state(test.type)); +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_durable_v2_open_lease_table(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + bool request_persistent, + struct durable_open_vs_lease *table, + uint8_t num_tests) +{ + bool ret = true; + uint8_t i; + + smb2_util_unlink(tree, fname); + + for (i = 0; i < num_tests; i++) { + ret = test_one_durable_v2_open_lease(tctx, + tree, + fname, + request_persistent, + table[i]); + if (ret == false) { + goto done; + } + } + +done: + smb2_util_unlink(tree, fname); + + return ret; +} + +bool test_durable_v2_open_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + char fname[256]; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8)); + + ret = test_durable_v2_open_lease_table(tctx, tree, fname, + false, /* request_persistent */ + durable_open_vs_lease_table, + NUM_LEASE_OPEN_TESTS); + + talloc_free(tree); + return ret; +} + +/** + * basic test for doing a durable open + * and do a durable reopen on the same connection + * while the first open is still active (fails) + */ +bool test_durable_v2_open_reopen1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + + /* try a durable reconnect while the file is still open */ + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * Basic test for doing a durable open + * and do a session reconnect while the first + * session is still active and the handle is + * still open in the client. + * This closes the original session and a + * durable reconnect on the new session succeeds. + */ +bool test_durable_v2_open_reopen1a(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; + uint64_t previous_session_id; + struct smbcli_options options; + struct GUID orig_client_guid; + + options = tree->session->transport->options; + orig_client_guid = options.client_guid; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen1a_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + + /* + * a session reconnect on a second tcp connection + */ + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* for oplocks, the client guid can be different: */ + options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + + TALLOC_FREE(tree); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 0); + _h = io.out.file.handle; + h = &_h; + + /* + * a session reconnect on a second tcp connection + */ + + previous_session_id = smb2cli_session_current_id(tree2->session->smbXcli); + + /* it works the same with the original guid */ + options.client_guid = orig_client_guid; + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 0); + _h = io.out.file.handle; + h = &_h; + +done: + if (tree == NULL) { + tree = tree2; + } + + if (tree == NULL) { + tree = tree3; + } + + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * lease variant of reopen1a + * + * Basic test for doing a durable open and doing a session + * reconnect while the first session is still active and the + * handle is still open in the client. + * This closes the original session and a durable reconnect on + * the new session succeeds depending on the client guid: + * + * Durable reconnect on a session with a different client guid fails. + * Durable reconnect on a session with the original client guid succeeds. + */ +bool test_durable_v2_open_reopen1a_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; + uint64_t previous_session_id; + struct smbcli_options options; + struct GUID orig_client_guid; + + options = tree->session->transport->options; + orig_client_guid = options.client_guid; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen1a_lease_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_create(&io, &ls, false /* dir */, fname, + lease_key, smb2_util_lease_state("RWH")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* + * a session reconnect on a second tcp connection + * with a different client_guid does not allow + * the durable reconnect. + */ + + options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree2); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + TALLOC_FREE(tree); + + /* + * but a durable reconnect on the new session with the wrong + * client guid fails + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + + /* + * now a session reconnect on a second tcp connection + * with original client_guid allows the durable reconnect. + */ + + options.client_guid = orig_client_guid; + //options.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree3); + torture_assert_goto(tctx, ret, ret, done, "couldn't reconnect"); + + /* + * check that this has deleted the old session + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + TALLOC_FREE(tree2); + + /* + * but a durable reconnect on the new session succeeds: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + status = smb2_create(tree3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 0); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + +done: + if (tree == NULL) { + tree = tree2; + } + + if (tree == NULL) { + tree = tree3; + } + + if (tree != NULL) { + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * basic test for doing a durable open + * tcp disconnect, reconnect, do a durable reopen (succeeds) + */ +bool test_durable_v2_open_reopen2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + struct GUID create_guid_invalid = GUID_random(); + bool ret = true; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + + /* disconnect, leaving the durable open */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * first a few failure cases + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* a non-zero but non-matching create_guid does not change it: */ + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid_invalid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * now success: + * The important difference is that the create_guid is provided. + */ + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + + /* disconnect one more time */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + /* These are completely ignored by the server */ + io.in.security_flags = 0x78; + io.in.oplock_level = 0x78; + io.in.impersonation_level = 0x12345678; + io.in.create_flags = 0x12345678; + io.in.reserved = 0x12345678; + io.in.desired_access = 0x12345678; + io.in.file_attributes = 0x12345678; + io.in.share_access = 0x12345678; + io.in.create_disposition = 0x12345678; + io.in.create_options = 0x12345678; + io.in.fname = "__non_existing_fname__"; + + /* + * only io.in.durable_handle_v2 and + * io.in.create_guid are checked + */ + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * durable reconnect test: + * connect with v2, reconnect with v1 + */ +bool test_durable_v2_open_reopen2b(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + struct smbcli_options options; + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen2b_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + + /* disconnect, leaving the durable open */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; /* durable v2 reconnect */ + io.in.create_guid = GUID_zero(); /* but zero create GUID */ + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = h; /* durable v1 (!) reconnect */ + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} +/** + * durable reconnect test: + * connect with v1, reconnect with v2 : fails (no create_guid...) + */ +bool test_durable_v2_open_reopen2c(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + struct smbcli_options options; + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen2c_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = true; + io.in.durable_open_v2 = false; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 0); + + /* disconnect, leaving the durable open */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; /* durable v2 reconnect */ + io.in.create_guid = create_guid; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * lease variant of reopen2 + * basic test for doing a durable open + * tcp disconnect, reconnect, do a durable reopen (succeeds) + */ +bool test_durable_v2_open_reopen2_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options = tree->session->transport->options; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_reopen2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = generate_random_u64(); + smb2_lease_create(&io, &ls, false /* dir */, fname, + lease_key, smb2_util_lease_state("RWH")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* a few failure tests: */ + + /* + * several attempts without lease attached: + * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND + * irrespective of file name provided + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * attempt with lease provided, but + * with a changed lease key. => fails + */ + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + /* a wrong lease key lets the request fail */ + ls.lease_key.data[0]++; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* restore the correct lease key */ + ls.lease_key.data[0]--; + + /* + * this last failing attempt is almost correct: + * only problem is: we use the wrong filename... + * Note that this gives INVALID_PARAMETER. + * This is different from oplocks! + */ + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* + * Now for a succeeding reconnect: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + + /* disconnect one more time */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * demonstrate that various parameters are ignored + * in the reconnect + */ + + ZERO_STRUCT(io); + /* + * These are completely ignored by the server + */ + io.in.security_flags = 0x78; + io.in.oplock_level = 0x78; + io.in.impersonation_level = 0x12345678; + io.in.create_flags = 0x12345678; + io.in.reserved = 0x12345678; + io.in.desired_access = 0x12345678; + io.in.file_attributes = 0x12345678; + io.in.share_access = 0x12345678; + io.in.create_disposition = 0x12345678; + io.in.create_options = 0x12345678; + + /* + * only these are checked: + * - io.in.fname + * - io.in.durable_handle_v2, + * - io.in.create_guid + * - io.in.lease_request->lease_key + */ + + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request = &ls; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response.lease_flags, 0); + CHECK_VAL(io.out.lease_response.lease_duration, 0); + + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * lease_v2 variant of reopen2 + * basic test for doing a durable open + * tcp disconnect, reconnect, do a durable reopen (succeeds) + */ +bool test_durable_v2_open_reopen2_lease_v2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options = tree->session->transport->options; + + smb2_deltree(tree, __func__); + status = torture_smb2_testdir(tree, __func__, &_h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree, _h); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\durable_v2_open_reopen2_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* a few failure tests: */ + + /* + * several attempts without lease attached: + * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND + * irrespective of file name provided + */ + + ZERO_STRUCT(io); + io.in.fname = ""; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle_v2 = h; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * attempt with lease provided, but + * with a changed lease key. => fails + */ + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + /* a wrong lease key lets the request fail */ + ls.lease_key.data[0]++; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* restore the correct lease key */ + ls.lease_key.data[0]--; + + + /* + * this last failing attempt is almost correct: + * only problem is: we use the wrong filename... + * Note that this gives INVALID_PARAMETER. + * This is different from oplocks! + */ + ZERO_STRUCT(io); + io.in.fname = "__non_existing_fname__"; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* + * Now for a succeeding reconnect: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response_v2.lease_flags, 0); + CHECK_VAL(io.out.lease_response_v2.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + + /* disconnect one more time */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * demonstrate that various parameters are ignored + * in the reconnect + */ + + ZERO_STRUCT(io); + /* + * These are completely ignored by the server + */ + io.in.security_flags = 0x78; + io.in.oplock_level = 0x78; + io.in.impersonation_level = 0x12345678; + io.in.create_flags = 0x12345678; + io.in.reserved = 0x12345678; + io.in.desired_access = 0x12345678; + io.in.file_attributes = 0x12345678; + io.in.share_access = 0x12345678; + io.in.create_disposition = 0x12345678; + io.in.create_options = 0x12345678; + io.in.fname = "__non_existing_fname__"; + + /* + * only these are checked: + * - io.in.fname + * - io.in.durable_handle_v2, + * - io.in.create_guid + * - io.in.lease_request_v2->lease_key + */ + + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request_v2 = &ls; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response_v2.lease_flags, 0); + CHECK_VAL(io.out.lease_response_v2.lease_duration, 0); + + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, __func__); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test durable request / reconnect with AppInstanceId + */ +bool test_durable_v2_open_app_instance(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1, _h2; + struct smb2_handle *h1 = NULL, *h2 = NULL; + struct smb2_create io1, io2; + bool ret = true; + struct GUID create_guid_1 = GUID_random(); + struct GUID create_guid_2 = GUID_random(); + struct GUID app_instance_id = GUID_random(); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_v2_open_app_instance_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + ZERO_STRUCT(break_info); + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid_1; + io1.in.app_instance_id = &app_instance_id; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io1.out.durable_open, false); + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.persistent_open, false); + CHECK_VAL(io1.out.timeout, 300*1000); + + /* + * try to open the file as durable from a second tree with + * a different create guid but the same app_instance_id + * while the first handle is still open. + */ + + smb2_oplock_create_share(&io2, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io2.in.durable_open = false; + io2.in.durable_open_v2 = true; + io2.in.persistent_open = false; + io2.in.create_guid = create_guid_2; + io2.in.app_instance_id = &app_instance_id; + io2.in.timeout = UINT32_MAX; + + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io2.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io2.out.durable_open, false); + CHECK_VAL(io2.out.durable_open_v2, true); + CHECK_VAL(io2.out.persistent_open, false); + CHECK_VAL(io2.out.timeout, 300*1000); + + CHECK_VAL(break_info.count, 0); + + status = smb2_util_close(tree1, *h1); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + h1 = NULL; + +done: + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + smb2_util_unlink(tree2, fname); + + talloc_free(tree1); + talloc_free(tree2); + + talloc_free(mem_ctx); + + return ret; +} + + +/** + * basic persistent open test. + * + * This test tests durable open with all possible oplock types. + */ + +struct durable_open_vs_oplock persistent_open_oplock_ca_table[NUM_OPLOCK_OPEN_TESTS] = +{ + { "", "", true, true }, + { "", "R", true, true }, + { "", "W", true, true }, + { "", "D", true, true }, + { "", "RD", true, true }, + { "", "RW", true, true }, + { "", "WD", true, true }, + { "", "RWD", true, true }, + + { "s", "", true, true }, + { "s", "R", true, true }, + { "s", "W", true, true }, + { "s", "D", true, true }, + { "s", "RD", true, true }, + { "s", "RW", true, true }, + { "s", "WD", true, true }, + { "s", "RWD", true, true }, + + { "x", "", true, true }, + { "x", "R", true, true }, + { "x", "W", true, true }, + { "x", "D", true, true }, + { "x", "RD", true, true }, + { "x", "RW", true, true }, + { "x", "WD", true, true }, + { "x", "RWD", true, true }, + + { "b", "", true, true }, + { "b", "R", true, true }, + { "b", "W", true, true }, + { "b", "D", true, true }, + { "b", "RD", true, true }, + { "b", "RW", true, true }, + { "b", "WD", true, true }, + { "b", "RWD", true, true }, +}; + +bool test_persistent_open_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + char fname[256]; + bool ret = true; + uint32_t share_capabilities; + bool share_is_ca = false; + struct durable_open_vs_oplock *table; + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "persistent_open_oplock_%s.dat", generate_random_str(tctx, 8)); + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_ca = share_capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY; + + if (share_is_ca) { + table = persistent_open_oplock_ca_table; + } else { + table = durable_open_vs_oplock_table; + } + + ret = test_durable_v2_open_oplock_table(tctx, tree, fname, + true, /* request_persistent */ + table, + NUM_OPLOCK_OPEN_TESTS); + + talloc_free(tree); + + return ret; +} + +/** + * basic persistent handle open test. + * persistent state should only be granted when requested + * along with a batch oplock or a handle lease. + * + * This test tests persistent open with all valid lease types. + */ + +struct durable_open_vs_lease persistent_open_lease_ca_table[NUM_LEASE_OPEN_TESTS] = +{ + { "", "", true, true }, + { "", "R", true, true }, + { "", "W", true, true }, + { "", "D", true, true }, + { "", "RW", true, true }, + { "", "RD", true, true }, + { "", "WD", true, true }, + { "", "RWD", true, true }, + + { "R", "", true, true }, + { "R", "R", true, true }, + { "R", "W", true, true }, + { "R", "D", true, true }, + { "R", "RW", true, true }, + { "R", "RD", true, true }, + { "R", "DW", true, true }, + { "R", "RWD", true, true }, + + { "RW", "", true, true }, + { "RW", "R", true, true }, + { "RW", "W", true, true }, + { "RW", "D", true, true }, + { "RW", "RW", true, true }, + { "RW", "RD", true, true }, + { "RW", "WD", true, true }, + { "RW", "RWD", true, true }, + + { "RH", "", true, true }, + { "RH", "R", true, true }, + { "RH", "W", true, true }, + { "RH", "D", true, true }, + { "RH", "RW", true, true }, + { "RH", "RD", true, true }, + { "RH", "WD", true, true }, + { "RH", "RWD", true, true }, + + { "RHW", "", true, true }, + { "RHW", "R", true, true }, + { "RHW", "W", true, true }, + { "RHW", "D", true, true }, + { "RHW", "RW", true, true }, + { "RHW", "RD", true, true }, + { "RHW", "WD", true, true }, + { "RHW", "RWD", true, true }, +}; + +bool test_persistent_open_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + char fname[256]; + bool ret = true; + uint32_t caps; + uint32_t share_capabilities; + bool share_is_ca; + struct durable_open_vs_lease *table; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "persistent_open_lease_%s.dat", generate_random_str(tctx, 8)); + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_ca = share_capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY; + + if (share_is_ca) { + table = persistent_open_lease_ca_table; + } else { + table = durable_open_vs_lease_table; + } + + ret = test_durable_v2_open_lease_table(tctx, tree, fname, + true, /* request_persistent */ + table, + NUM_LEASE_OPEN_TESTS); + + talloc_free(tree); + + return ret; +} + +/** + * setfileinfo test for doing a durable open + * create the file with lease and durable handle, + * write to it (via set end-of-file), tcp disconnect, + * reconnect, do a durable reopen - should succeed. + * + * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15022 + */ +bool test_durable_v2_setinfo(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + union smb_setfileinfo si; + struct GUID create_guid = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options = tree->session->transport->options; + + smb2_deltree(tree, __func__); + status = torture_smb2_testdir(tree, __func__, &_h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree, _h); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\durable_v2_setinfo%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + + /* + * Set EOF to 0x100000. + * Mimics an Apple client test, but most importantly + * causes the mtime timestamp on disk to be updated. + */ + ZERO_STRUCT(si); + si.generic.level = SMB_SFILEINFO_END_OF_FILE_INFORMATION; + si.generic.in.file.handle = io.out.file.handle; + si.end_of_file_info.in.size = 0x100000; + status = smb2_setinfo_file(tree, &si); + CHECK_STATUS(status, NT_STATUS_OK); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* + * Now for a succeeding reconnect: + */ + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h; + io.in.create_guid = create_guid; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + /* the requested lease state is irrelevant */ + ls.lease_state = smb2_util_lease_state(""); + + h = NULL; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_VAL(io.out.size, 0x100000); \ + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key); + CHECK_VAL(io.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH")); + CHECK_VAL(io.out.lease_response_v2.lease_flags, 0); + CHECK_VAL(io.out.lease_response_v2.lease_duration, 0); + _h = io.out.file.handle; + h = &_h; + +done: + + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, __func__); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +struct torture_suite *torture_smb2_durable_v2_open_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "durable-v2-open"); + + torture_suite_add_1smb2_test(suite, "create-blob", test_durable_v2_open_create_blob); + torture_suite_add_1smb2_test(suite, "open-oplock", test_durable_v2_open_oplock); + torture_suite_add_1smb2_test(suite, "open-lease", test_durable_v2_open_lease); + torture_suite_add_1smb2_test(suite, "reopen1", test_durable_v2_open_reopen1); + torture_suite_add_1smb2_test(suite, "reopen1a", test_durable_v2_open_reopen1a); + torture_suite_add_1smb2_test(suite, "reopen1a-lease", test_durable_v2_open_reopen1a_lease); + torture_suite_add_1smb2_test(suite, "reopen2", test_durable_v2_open_reopen2); + torture_suite_add_1smb2_test(suite, "reopen2b", test_durable_v2_open_reopen2b); + torture_suite_add_1smb2_test(suite, "reopen2c", test_durable_v2_open_reopen2c); + torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_v2_open_reopen2_lease); + torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_v2_open_reopen2_lease_v2); + torture_suite_add_1smb2_test(suite, "durable-v2-setinfo", test_durable_v2_setinfo); + torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance); + torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock); + torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease); + + suite->description = talloc_strdup(suite, "SMB2-DURABLE-V2-OPEN tests"); + + return suite; +} + +/** + * basic test for doing a durable open + * tcp disconnect, reconnect, do a durable reopen (succeeds) + */ +static bool test_durable_v2_reconnect_delay(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + struct smbcli_options options; + uint64_t previous_session_id; + uint8_t b = 0; + bool ret = true; + bool ok; + + options = tree->session->transport->options; + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, + sizeof(fname), + "durable_v2_reconnect_delay_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = 0; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + _h = io.out.file.handle; + h = &_h; + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + + status = smb2_util_write(tree, *h, &b, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + + /* disconnect, leaving the durable open */ + TALLOC_FREE(tree); + h = NULL; + + ok = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree); + torture_assert_goto(tctx, ok, ret, done, "couldn't reconnect, bailing\n"); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = &_h; + io.in.create_guid = create_guid; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + TALLOC_FREE(tree); + + smb2_util_unlink(tree2, fname); + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * basic test for doing a durable open with 1msec cleanup time + * tcp disconnect, wait a bit, reconnect, do a durable reopen (fails) + */ +static bool test_durable_v2_reconnect_delay_msec(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct smb2_lease ls; + struct GUID create_guid = GUID_random(); + struct smbcli_options options; + uint64_t previous_session_id; + uint8_t b = 0; + bool ret = true; + bool ok; + + options = tree->session->transport->options; + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, + sizeof(fname), + "durable_v2_reconnect_delay_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_lease_create( + &io, + &ls, + false /* dir */, + fname, + generate_random_u64(), + smb2_util_lease_state("RWH")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = 1; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + + _h = io.out.file.handle; + h = &_h; + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.durable_open_v2, true); + + status = smb2_util_write(tree, *h, &b, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + + /* disconnect, leaving the durable open */ + TALLOC_FREE(tree); + h = NULL; + + ok = torture_smb2_connection_ext(tctx, previous_session_id, + &options, &tree); + torture_assert_goto(tctx, ok, ret, done, "couldn't reconnect, bailing\n"); + + sleep(10); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = &_h; + io.in.create_guid = create_guid; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + _h = io.out.file.handle; + h = &_h; + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + TALLOC_FREE(tree); + + smb2_util_unlink(tree2, fname); + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +struct torture_suite *torture_smb2_durable_v2_delay_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "durable-v2-delay"); + + torture_suite_add_2smb2_test(suite, + "durable_v2_reconnect_delay", + test_durable_v2_reconnect_delay); + torture_suite_add_2smb2_test(suite, + "durable_v2_reconnect_delay_msec", + test_durable_v2_reconnect_delay_msec); + + return suite; +} diff --git a/source4/torture/smb2/ea.c b/source4/torture/smb2/ea.c new file mode 100644 index 0000000..becacae --- /dev/null +++ b/source4/torture/smb2/ea.c @@ -0,0 +1,152 @@ +/* + Unix SMB/CIFS implementation. + SMB2 EA tests + + Copyright (C) Ralph Boehme 2022 + + 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/>. +*/ + +#include "includes.h" +#include "ntstatus_gen.h" +#include "system/time.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +#define BASEDIR "test_ea" + +static bool find_returned_ea(union smb_fileinfo *finfo2, + const char *eaname) +{ + unsigned int i; + unsigned int num_eas = finfo2->all_eas.out.num_eas; + struct ea_struct *eas = finfo2->all_eas.out.eas; + + for (i = 0; i < num_eas; i++) { + if (eas[i].name.s == NULL) { + continue; + } + /* Windows capitalizes returned EA names. */ + if (strequal(eas[i].name.s, eaname)) { + return true; + } + } + return false; +} + +static bool torture_smb2_acl_xattr(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = BASEDIR "\\test_acl_xattr"; + const char *xattr_name = NULL; + struct smb2_handle h1; + struct ea_struct ea; + union smb_fileinfo finfo; + union smb_setfileinfo sfinfo; + NTSTATUS status; + bool ret = true; + + torture_comment(tctx, "Verify NTACL xattr can't be accessed\n"); + + xattr_name = torture_setting_string(tctx, "acl_xattr_name", NULL); + torture_assert_not_null(tctx, xattr_name, "Missing acl_xattr_name option\n"); + + smb2_deltree(tree, BASEDIR); + + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + smb2_util_close(tree, h1); + + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testfile failed\n"); + + /* + * 1. Set an EA, so we have somthing to list + */ + ZERO_STRUCT(ea); + ea.name.s = "void"; + ea.name.private_length = strlen("void") + 1; + ea.value = data_blob_string_const("testme"); + + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_FULL_EA_INFORMATION; + sfinfo.generic.in.file.handle = h1; + sfinfo.full_ea_information.in.eas.num_eas = 1; + sfinfo.full_ea_information.in.eas.eas = &ea; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Setting EA should faild\n"); + + /* + * 2. Verify NT ACL EA is not listed + */ + ZERO_STRUCT(finfo); + finfo.generic.level = RAW_FILEINFO_SMB2_ALL_EAS; + finfo.generic.in.file.handle = h1; + + status = smb2_getinfo_file(tree, tctx, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir\n"); + + if (find_returned_ea(&finfo, xattr_name)) { + torture_result(tctx, TORTURE_FAIL, + "%s: NTACL EA leaked\n", + __location__); + ret = false; + goto done; + } + + /* + * 3. Try to set EA, should fail + */ + ZERO_STRUCT(ea); + ea.name.s = xattr_name; + ea.name.private_length = strlen(xattr_name) + 1; + ea.value = data_blob_string_const("testme"); + + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_FULL_EA_INFORMATION; + sfinfo.generic.in.file.handle = h1; + sfinfo.full_ea_information.in.eas.num_eas = 1; + sfinfo.full_ea_information.in.eas.eas = &ea; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_equal_goto( + tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, "Setting EA should fail\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + + smb2_deltree(tree, BASEDIR); + + return ret; +} + +struct torture_suite *torture_smb2_ea(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "ea"); + suite->description = talloc_strdup(suite, "SMB2-EA tests"); + + torture_suite_add_1smb2_test(suite, "acl_xattr", torture_smb2_acl_xattr); + + return suite; +} diff --git a/source4/torture/smb2/getinfo.c b/source4/torture/smb2/getinfo.c new file mode 100644 index 0000000..539090a --- /dev/null +++ b/source4/torture/smb2/getinfo.c @@ -0,0 +1,951 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 getinfo test suite + + Copyright (C) Andrew Tridgell 2005 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb/smbXcli_base.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/util.h" + +static struct { + const char *name; + uint16_t level; + NTSTATUS fstatus; + NTSTATUS dstatus; + union smb_fileinfo finfo; + union smb_fileinfo dinfo; +} file_levels[] = { +#define LEVEL(x) .name = #x, .level = x + { LEVEL(RAW_FILEINFO_BASIC_INFORMATION) }, + { LEVEL(RAW_FILEINFO_STANDARD_INFORMATION) }, + { LEVEL(RAW_FILEINFO_INTERNAL_INFORMATION) }, + { LEVEL(RAW_FILEINFO_EA_INFORMATION) }, + { LEVEL(RAW_FILEINFO_ACCESS_INFORMATION) }, + { LEVEL(RAW_FILEINFO_POSITION_INFORMATION) }, + { LEVEL(RAW_FILEINFO_MODE_INFORMATION) }, + { LEVEL(RAW_FILEINFO_ALIGNMENT_INFORMATION) }, + { LEVEL(RAW_FILEINFO_ALL_INFORMATION) }, + { LEVEL(RAW_FILEINFO_ALT_NAME_INFORMATION) }, + { LEVEL(RAW_FILEINFO_STREAM_INFORMATION) }, + { LEVEL(RAW_FILEINFO_COMPRESSION_INFORMATION) }, + { LEVEL(RAW_FILEINFO_NETWORK_OPEN_INFORMATION) }, + { LEVEL(RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION) }, + + { LEVEL(RAW_FILEINFO_SMB2_ALL_EAS) }, + + { LEVEL(RAW_FILEINFO_SMB2_ALL_INFORMATION) }, + { LEVEL(RAW_FILEINFO_SEC_DESC) } +}; + +static struct { + const char *name; + uint16_t level; + NTSTATUS status; + union smb_fsinfo info; +} fs_levels[] = { + { LEVEL(RAW_QFS_VOLUME_INFORMATION) }, + { LEVEL(RAW_QFS_SIZE_INFORMATION) }, + { LEVEL(RAW_QFS_DEVICE_INFORMATION) }, + { LEVEL(RAW_QFS_ATTRIBUTE_INFORMATION) }, + { LEVEL(RAW_QFS_QUOTA_INFORMATION) }, + { LEVEL(RAW_QFS_FULL_SIZE_INFORMATION) }, + { LEVEL(RAW_QFS_OBJECTID_INFORMATION) }, + { LEVEL(RAW_QFS_SECTOR_SIZE_INFORMATION) }, +}; + +#define FNAME "testsmb2_file.dat" +#define DNAME "testsmb2_dir" + +/* + test fileinfo levels +*/ +static bool torture_smb2_fileinfo(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_handle hfile, hdir; + NTSTATUS status; + int i; + + status = torture_smb2_testfile(tree, FNAME, &hfile); + torture_assert_ntstatus_ok(tctx, status, "Unable to create test file " + FNAME "\n"); + + status = torture_smb2_testdir(tree, DNAME, &hdir); + torture_assert_ntstatus_ok(tctx, status, "Unable to create test dir " + DNAME "\n"); + + torture_comment(tctx, "Testing file info levels\n"); + torture_smb2_all_info(tctx, tree, hfile); + torture_smb2_all_info(tctx, tree, hdir); + + for (i=0;i<ARRAY_SIZE(file_levels);i++) { + if (file_levels[i].level == RAW_FILEINFO_SEC_DESC) { + file_levels[i].finfo.query_secdesc.in.secinfo_flags = 0x7; + file_levels[i].dinfo.query_secdesc.in.secinfo_flags = 0x7; + } + if (file_levels[i].level == RAW_FILEINFO_SMB2_ALL_EAS) { + file_levels[i].finfo.all_eas.in.continue_flags = + SMB2_CONTINUE_FLAG_RESTART; + file_levels[i].dinfo.all_eas.in.continue_flags = + SMB2_CONTINUE_FLAG_RESTART; + } + file_levels[i].finfo.generic.level = file_levels[i].level; + file_levels[i].finfo.generic.in.file.handle = hfile; + file_levels[i].fstatus = smb2_getinfo_file(tree, tree, &file_levels[i].finfo); + torture_assert_ntstatus_ok(tctx, file_levels[i].fstatus, + talloc_asprintf(tctx, "%s on file", + file_levels[i].name)); + file_levels[i].dinfo.generic.level = file_levels[i].level; + file_levels[i].dinfo.generic.in.file.handle = hdir; + file_levels[i].dstatus = smb2_getinfo_file(tree, tree, &file_levels[i].dinfo); + torture_assert_ntstatus_ok(tctx, file_levels[i].dstatus, + talloc_asprintf(tctx, "%s on dir", + file_levels[i].name)); + } + + return true; +} + +/* + test granted access when desired access includes + FILE_EXECUTE and does not include FILE_READ_DATA +*/ +static bool torture_smb2_fileinfo_grant_read(struct torture_context *tctx) +{ + struct smb2_tree *tree; + bool ret; + struct smb2_handle hfile, hdir; + NTSTATUS status; + uint32_t file_granted_access, dir_granted_access; + + ret = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ret, "connection failed"); + + status = torture_smb2_testfile_access( + tree, FNAME, &hfile, SEC_FILE_EXECUTE | SEC_FILE_READ_ATTRIBUTE); + torture_assert_ntstatus_ok(tctx, status, + "Unable to create test file " FNAME "\n"); + status = + torture_smb2_get_allinfo_access(tree, hfile, &file_granted_access); + torture_assert_ntstatus_ok(tctx, status, + "Unable to query test file access "); + torture_assert_int_equal(tctx, file_granted_access, + SEC_FILE_EXECUTE | SEC_FILE_READ_ATTRIBUTE, + "granted file access "); + smb2_util_close(tree, hfile); + + status = torture_smb2_testdir_access( + tree, DNAME, &hdir, SEC_FILE_EXECUTE | SEC_FILE_READ_ATTRIBUTE); + torture_assert_ntstatus_ok(tctx, status, + "Unable to create test dir " DNAME "\n"); + status = + torture_smb2_get_allinfo_access(tree, hdir, &dir_granted_access); + torture_assert_ntstatus_ok(tctx, status, + "Unable to query test dir access "); + torture_assert_int_equal(tctx, dir_granted_access, + SEC_FILE_EXECUTE | SEC_FILE_READ_ATTRIBUTE, + "granted dir access "); + smb2_util_close(tree, hdir); + + return true; +} + +static bool torture_smb2_fileinfo_normalized(struct torture_context *tctx) +{ + struct smb2_tree *tree = NULL; + bool ret; + struct smb2_handle hroot; + const char *d1 = NULL, *d1l = NULL, *d1u = NULL; + struct smb2_handle hd1, hd1l, hd1u; + const char *d2 = NULL, *d2l = NULL, *d2u = NULL; + struct smb2_handle hd2, hd2l, hd2u; + const char *d3 = NULL, *d3l = NULL, *d3u = NULL; + struct smb2_handle hd3, hd3l, hd3u; + const char *d3s = NULL, *d3sl = NULL, *d3su = NULL, *d3sd = NULL; + struct smb2_handle hd3s, hd3sl, hd3su, hd3sd; + const char *f4 = NULL, *f4l = NULL, *f4u = NULL, *f4d = NULL; + struct smb2_handle hf4, hf4l, hf4u, hf4d; + const char *f4s = NULL, *f4sl = NULL, *f4su = NULL, *f4sd = NULL; + struct smb2_handle hf4s, hf4sl, hf4su, hf4sd; + union smb_fileinfo info = { + .normalized_name_info = { + .level = RAW_FILEINFO_NORMALIZED_NAME_INFORMATION, + }, + }; + NTSTATUS status; + enum protocol_types protocol; + struct smb2_tree *tree_3_0 = NULL; + struct smbcli_options options3_0; + struct smb2_handle hroot_3_0; + + ret = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ret, "connection failed"); + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + + d1 = talloc_asprintf(tctx, "torture_dIr1N"); + torture_assert_not_null(tctx, d1, "d1"); + d1l = strlower_talloc(tctx, d1); + torture_assert_not_null(tctx, d1l, "d1l"); + d1u = strupper_talloc(tctx, d1); + torture_assert_not_null(tctx, d1u, "d1u"); + + d2 = talloc_asprintf(tctx, "%s\\dIr2Na", d1); + torture_assert_not_null(tctx, d2, "d2"); + d2l = strlower_talloc(tctx, d2); + torture_assert_not_null(tctx, d2l, "d2l"); + d2u = strupper_talloc(tctx, d2); + torture_assert_not_null(tctx, d2u, "d2u"); + + d3 = talloc_asprintf(tctx, "%s\\dIr3NaM", d2); + torture_assert_not_null(tctx, d3, "d3"); + d3l = strlower_talloc(tctx, d3); + torture_assert_not_null(tctx, d3l, "d3l"); + d3u = strupper_talloc(tctx, d3); + torture_assert_not_null(tctx, d3u, "d3u"); + + d3s = talloc_asprintf(tctx, "%s:sTrEaM3", d3); + torture_assert_not_null(tctx, d3s, "d3s"); + d3sl = strlower_talloc(tctx, d3s); + torture_assert_not_null(tctx, d3sl, "d3sl"); + d3su = strupper_talloc(tctx, d3s); + torture_assert_not_null(tctx, d3su, "d3su"); + d3sd = talloc_asprintf(tctx, "%s:$DaTa", d3s); + torture_assert_not_null(tctx, d3sd, "d3sd"); + + f4 = talloc_asprintf(tctx, "%s\\fIlE4NaMe", d3); + torture_assert_not_null(tctx, f4, "f4"); + f4l = strlower_talloc(tctx, f4); + torture_assert_not_null(tctx, f4l, "f4l"); + f4u = strupper_talloc(tctx, f4); + torture_assert_not_null(tctx, f4u, "f4u"); + f4d = talloc_asprintf(tctx, "%s::$dAtA", f4); + torture_assert_not_null(tctx, f4d, "f4d"); + + f4s = talloc_asprintf(tctx, "%s:StReAm4", f4); + torture_assert_not_null(tctx, f4s, "f4s"); + f4sl = strlower_talloc(tctx, f4s); + torture_assert_not_null(tctx, f4sl, "f4sl"); + f4su = strupper_talloc(tctx, f4s); + torture_assert_not_null(tctx, f4su, "f4su"); + f4sd = talloc_asprintf(tctx, "%s:$dAtA", f4s); + torture_assert_not_null(tctx, f4sd, "f4sd"); + + status = smb2_util_roothandle(tree, &hroot); + torture_assert_ntstatus_ok(tctx, status, "Unable to create root handle"); + + info.normalized_name_info.in.file.handle = hroot; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + if (protocol < PROTOCOL_SMB3_11) { + /* + * Only SMB 3.1.1 and above should offer this. + */ + torture_assert_ntstatus_equal(tctx, status, + NT_STATUS_NOT_SUPPORTED, + "getinfo hroot"); + torture_skip(tctx, "SMB 3.1.1 not supported"); + } + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + /* + * Not all servers support this. + * (only Windows 10 1803 and higher) + */ + torture_skip(tctx, "NORMALIZED_NAME_INFORMATION not supported"); + } + torture_assert_ntstatus_ok(tctx, status, "getinfo hroot"); + torture_assert(tctx, info.normalized_name_info.out.fname.s == NULL, + "getinfo hroot should be empty"); + + smb2_deltree(tree, d1); + + status = torture_smb2_testdir(tree, d1, &hd1); + torture_assert_ntstatus_ok(tctx, status, "Unable to create hd1"); + status = torture_smb2_open(tree, d1l, SEC_RIGHTS_FILE_ALL, &hd1l); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd1l"); + status = torture_smb2_open(tree, d1u, SEC_RIGHTS_FILE_ALL, &hd1u); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd1u"); + + status = torture_smb2_testdir(tree, d2, &hd2); + torture_assert_ntstatus_ok(tctx, status, "Unable to create hd2"); + status = torture_smb2_open(tree, d2l, SEC_RIGHTS_FILE_ALL, &hd2l); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd2l"); + status = torture_smb2_open(tree, d2u, SEC_RIGHTS_FILE_ALL, &hd2u); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd2u"); + + status = torture_smb2_testdir(tree, d3, &hd3); + torture_assert_ntstatus_ok(tctx, status, "Unable to create hd3"); + status = torture_smb2_open(tree, d3l, SEC_RIGHTS_FILE_ALL, &hd3l); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd3l"); + status = torture_smb2_open(tree, d3u, SEC_RIGHTS_FILE_ALL, &hd3u); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd3u"); + + status = torture_smb2_testfile(tree, d3s, &hd3s); + torture_assert_ntstatus_ok(tctx, status, "Unable to create hd3s"); + status = torture_smb2_open(tree, d3sl, SEC_RIGHTS_FILE_ALL, &hd3sl); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd3sl"); + status = torture_smb2_open(tree, d3su, SEC_RIGHTS_FILE_ALL, &hd3su); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd3su"); + status = torture_smb2_open(tree, d3sd, SEC_RIGHTS_FILE_ALL, &hd3sd); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hd3sd"); + + status = torture_smb2_testfile(tree, f4, &hf4); + torture_assert_ntstatus_ok(tctx, status, "Unable to create hf4"); + status = torture_smb2_open(tree, f4l, SEC_RIGHTS_FILE_ALL, &hf4l); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hf4l"); + status = torture_smb2_open(tree, f4u, SEC_RIGHTS_FILE_ALL, &hf4u); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hf4u"); + status = torture_smb2_open(tree, f4d, SEC_RIGHTS_FILE_ALL, &hf4d); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hf4d"); + + status = torture_smb2_testfile(tree, f4s, &hf4s); + torture_assert_ntstatus_ok(tctx, status, "Unable to create hf4s"); + status = torture_smb2_open(tree, f4sl, SEC_RIGHTS_FILE_ALL, &hf4sl); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hf4sl"); + status = torture_smb2_open(tree, f4su, SEC_RIGHTS_FILE_ALL, &hf4su); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hf4su"); + status = torture_smb2_open(tree, f4sd, SEC_RIGHTS_FILE_ALL, &hf4sd); + torture_assert_ntstatus_ok(tctx, status, "Unable to open hf4sd"); + + info.normalized_name_info.in.file.handle = hd1; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd1"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d1, "getinfo hd1"); + info.normalized_name_info.in.file.handle = hd1l; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd1l"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d1, "getinfo hd1l"); + info.normalized_name_info.in.file.handle = hd1u; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd1u"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d1, "getinfo hd1u"); + + info.normalized_name_info.in.file.handle = hd2; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd2"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d2, "getinfo hd2"); + info.normalized_name_info.in.file.handle = hd2l; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd2l"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d2, "getinfo hd2l"); + info.normalized_name_info.in.file.handle = hd2u; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd2u"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d2, "getinfo hd2u"); + + info.normalized_name_info.in.file.handle = hd3; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd3"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d3, "getinfo hd3"); + info.normalized_name_info.in.file.handle = hd3l; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd3l"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d3, "getinfo hd3l"); + info.normalized_name_info.in.file.handle = hd3u; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd3u"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d3, "getinfo hd3u"); + + info.normalized_name_info.in.file.handle = hd3s; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd3s"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d3s, "getinfo hd3s"); + info.normalized_name_info.in.file.handle = hd3sl; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd3sl"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d3s, "getinfo hd3sl"); + info.normalized_name_info.in.file.handle = hd3su; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd3su"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d3s, "getinfo hd3su"); + info.normalized_name_info.in.file.handle = hd3sd; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hd3sd"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + d3s, "getinfo hd3sd"); + + info.normalized_name_info.in.file.handle = hf4; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4, "getinfo hf4"); + info.normalized_name_info.in.file.handle = hf4l; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4l"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4, "getinfo hf4l"); + info.normalized_name_info.in.file.handle = hf4u; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4u"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4, "getinfo hf4u"); + info.normalized_name_info.in.file.handle = hf4d; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4d"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4, "getinfo hf4d"); + + info.normalized_name_info.in.file.handle = hf4s; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4s"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4s, "getinfo hf4s"); + info.normalized_name_info.in.file.handle = hf4sl; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4sl"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4s, "getinfo hf4sl"); + info.normalized_name_info.in.file.handle = hf4su; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4su"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4s, "getinfo hf4su"); + info.normalized_name_info.in.file.handle = hf4sd; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree, tree, &info); + torture_assert_ntstatus_ok(tctx, status, "getinfo hf4sd"); + torture_assert_str_equal(tctx, info.normalized_name_info.out.fname.s, + f4s, "getinfo hf4sd"); + + /* Set max protocol to SMB 3.0.2 */ + options3_0 = tree->session->transport->options; + options3_0.max_protocol = PROTOCOL_SMB3_02; + options3_0.client_guid = GUID_zero(); + ret = torture_smb2_connection_ext(tctx, 0, &options3_0, &tree_3_0); + torture_assert(tctx, ret, "connection with SMB < 3.1.1 failed"); + + status = smb2_util_roothandle(tree_3_0, &hroot_3_0); + torture_assert_ntstatus_ok(tctx, status, "Unable to create root handle 3_0"); + + info.normalized_name_info.in.file.handle = hroot_3_0; + ZERO_STRUCT(info.normalized_name_info.out); + status = smb2_getinfo_file(tree_3_0, tree_3_0, &info); + torture_assert_ntstatus_equal(tctx, status, + NT_STATUS_NOT_SUPPORTED, + "getinfo hroot"); + + return true; +} + +/* + test fsinfo levels +*/ +static bool torture_smb2_fsinfo(struct torture_context *tctx) +{ + bool ret; + struct smb2_tree *tree; + int i; + NTSTATUS status; + struct smb2_handle handle; + + torture_comment(tctx, "Testing fsinfo levels\n"); + + ret = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ret, "connection failed"); + + status = smb2_util_roothandle(tree, &handle); + torture_assert_ntstatus_ok(tctx, status, "Unable to create root handle"); + + for (i=0;i<ARRAY_SIZE(fs_levels);i++) { + fs_levels[i].info.generic.level = fs_levels[i].level; + fs_levels[i].info.generic.handle = handle; + fs_levels[i].status = smb2_getinfo_fs(tree, tree, &fs_levels[i].info); + torture_assert_ntstatus_ok(tctx, fs_levels[i].status, + fs_levels[i].name); + } + + return true; +} + +static bool torture_smb2_buffercheck_err(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_getinfo *b, + size_t fixed, + DATA_BLOB full) +{ + size_t i; + + for (i=0; i<=full.length; i++) { + NTSTATUS status; + + b->in.output_buffer_length = i; + + status = smb2_getinfo(tree, tree, b); + + if (i < fixed) { + torture_assert_ntstatus_equal( + tctx, status, NT_STATUS_INFO_LENGTH_MISMATCH, + "Wrong error code small buffer"); + continue; + } + + if (i<full.length) { + torture_assert_ntstatus_equal( + tctx, status, STATUS_BUFFER_OVERFLOW, + "Wrong error code for large buffer"); + /* + * TODO: compare the output buffer. That seems a bit + * difficult, because for level 5 for example the + * label length is adjusted to what is there. And some + * reserved fields seem to be not initialized to 0. + */ + TALLOC_FREE(b->out.blob.data); + continue; + } + + torture_assert_ntstatus_equal( + tctx, status, NT_STATUS_OK, + "Wrong error code for right sized buffer"); + } + + return true; +} + +struct level_buffersize { + int level; + size_t fixed; +}; + +static bool torture_smb2_qfs_buffercheck(struct torture_context *tctx) +{ + bool ret; + struct smb2_tree *tree; + NTSTATUS status; + struct smb2_handle handle; + int i; + + struct level_buffersize levels[] = { + { 1, 24 }, /* We don't have proper defines here */ + { 3, 24 }, + { 4, 8 }, + { 5, 16 }, + { 6, 48 }, + { 7, 32 }, + { 11, 28 }, + }; + + torture_comment(tctx, "Testing SMB2_GETINFO_FS buffer sizes\n"); + + ret = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ret, "connection failed"); + + status = smb2_util_roothandle(tree, &handle); + torture_assert_ntstatus_ok( + tctx, status, "Unable to create root handle"); + + for (i=0; i<ARRAY_SIZE(levels); i++) { + struct smb2_getinfo b; + + if (TARGET_IS_SAMBA3(tctx) && + ((levels[i].level == 6) || (levels[i].level == 11))) { + continue; + } + + ZERO_STRUCT(b); + b.in.info_type = SMB2_0_INFO_FILESYSTEM; + b.in.info_class = levels[i].level; + b.in.file.handle = handle; + b.in.output_buffer_length = 65535; + + status = smb2_getinfo(tree, tree, &b); + + torture_assert_ntstatus_equal( + tctx, status, NT_STATUS_OK, + "Wrong error code for large buffer"); + + ret = torture_smb2_buffercheck_err( + tctx, tree, &b, levels[i].fixed, b.out.blob); + if (!ret) { + return ret; + } + } + + return true; +} + +static bool torture_smb2_qfile_buffercheck(struct torture_context *tctx) +{ + bool ret; + struct smb2_tree *tree; + struct smb2_create c; + NTSTATUS status; + struct smb2_handle handle; + int i; + + struct level_buffersize levels[] = { + { 4, 40 }, + { 5, 24 }, + { 6, 8 }, + { 7, 4 }, + { 8, 4 }, + { 16, 4 }, + { 17, 4 }, + { 18, 104 }, + { 21, 8 }, + { 22, 32 }, + { 28, 16 }, + { 34, 56 }, + { 35, 8 }, + }; + + torture_comment(tctx, "Testing SMB2_GETINFO_FILE buffer sizes\n"); + + ret = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ret, "connection failed"); + + ZERO_STRUCT(c); + c.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + c.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + c.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + c.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + c.in.create_options = 0; + c.in.fname = "bufsize.txt"; + + c.in.eas.num_eas = 2; + c.in.eas.eas = talloc_array(tree, struct ea_struct, 2); + c.in.eas.eas[0].flags = 0; + c.in.eas.eas[0].name.s = "EAONE"; + c.in.eas.eas[0].value = data_blob_talloc(c.in.eas.eas, "VALUE1", 6); + c.in.eas.eas[1].flags = 0; + c.in.eas.eas[1].name.s = "SECONDEA"; + c.in.eas.eas[1].value = data_blob_talloc(c.in.eas.eas, "ValueTwo", 8); + + status = smb2_create(tree, tree, &c); + torture_assert_ntstatus_ok( + tctx, status, "Unable to create test file"); + + handle = c.out.file.handle; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + struct smb2_getinfo b; + + ZERO_STRUCT(b); + b.in.info_type = SMB2_0_INFO_FILE; + b.in.info_class = levels[i].level; + b.in.file.handle = handle; + b.in.output_buffer_length = 65535; + + status = smb2_getinfo(tree, tree, &b); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) { + continue; + } + torture_assert_ntstatus_equal( + tctx, status, NT_STATUS_OK, + "Wrong error code for large buffer"); + + ret = torture_smb2_buffercheck_err( + tctx, tree, &b, levels[i].fixed, b.out.blob); + if (!ret) { + return ret; + } + } + return true; +} + +static bool torture_smb2_qsec_buffercheck(struct torture_context *tctx) +{ + struct smb2_getinfo b; + bool ret; + struct smb2_tree *tree; + struct smb2_create c; + NTSTATUS status; + struct smb2_handle handle; + + torture_comment(tctx, "Testing SMB2_GETINFO_SECURITY buffer sizes\n"); + + ret = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ret, "connection failed"); + + ZERO_STRUCT(c); + c.in.oplock_level = 0; + c.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_LIST | SEC_STD_READ_CONTROL; + c.in.file_attributes = 0; + c.in.create_disposition = NTCREATEX_DISP_OPEN; + c.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_DELETE; + c.in.create_options = NTCREATEX_OPTIONS_ASYNC_ALERT; + c.in.fname = ""; + + status = smb2_create(tree, tree, &c); + torture_assert_ntstatus_ok( + tctx, status, "Unable to create root handle"); + + handle = c.out.file.handle; + + ZERO_STRUCT(b); + b.in.info_type = SMB2_0_INFO_SECURITY; + b.in.info_class = 0; + b.in.file.handle = handle; + b.in.output_buffer_length = 0; + + status = smb2_getinfo(tree, tree, &b); + torture_assert_ntstatus_equal( + tctx, status, NT_STATUS_BUFFER_TOO_SMALL, + "Wrong error code for large buffer"); + + b.in.output_buffer_length = 1; + status = smb2_getinfo(tree, tree, &b); + torture_assert_ntstatus_equal( + tctx, status, NT_STATUS_BUFFER_TOO_SMALL, + "Wrong error code for large buffer"); + + return true; +} + +/* basic testing of all SMB2 getinfo levels +*/ +static bool torture_smb2_getinfo(struct torture_context *tctx) +{ + struct smb2_tree *tree; + bool ret = true; + NTSTATUS status; + + ret = torture_smb2_connection(tctx, &tree); + torture_assert(tctx, ret, "connection failed"); + + smb2_deltree(tree, FNAME); + smb2_deltree(tree, DNAME); + + status = torture_setup_complex_file(tctx, tree, FNAME); + torture_assert_ntstatus_ok(tctx, status, + "setup complex file " FNAME); + + status = torture_setup_complex_file(tctx, tree, FNAME ":streamtwo"); + torture_assert_ntstatus_ok(tctx, status, + "setup complex file " FNAME ":streamtwo"); + + status = torture_setup_complex_dir(tctx, tree, DNAME); + torture_assert_ntstatus_ok(tctx, status, + "setup complex dir " DNAME); + + status = torture_setup_complex_file(tctx, tree, DNAME ":streamtwo"); + torture_assert_ntstatus_ok(tctx, status, + "setup complex dir " DNAME ":streamtwo"); + + ret &= torture_smb2_fileinfo(tctx, tree); + + return ret; +} + +#undef LEVEL +#define LEVEL(l, u, ua, ra) \ + .name = #l, \ + .level = l, \ + .unrestricted = u, \ + .unrestricted_access = ua, \ + .required_access = ra + +static struct { + const char *name; + uint16_t level; + bool unrestricted; + uint32_t unrestricted_access; + uint32_t required_access; +} file_levels_access[] = { + /* + * The following info levels are not checked: + * - FileFullEaInformation and FileIdInformation: + * not implemented by the s4/libcli/raw + * - all pipe infolevels: that should be tested elsewhere by RPC tests + */ + + /* + * The following allow unrestricted access, so requesting + * SEC_FILE_READ_ATTRIBUTE works, SEC_FILE_READ_ATTRIBUTE or + * SEC_FILE_READ_EA as well of course. + */ + { LEVEL(RAW_FILEINFO_STANDARD_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_INTERNAL_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_ACCESS_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_POSITION_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_MODE_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_ALIGNMENT_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_ALT_NAME_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_STREAM_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_COMPRESSION_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_NORMALIZED_NAME_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_EA_INFORMATION, true, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_EA) }, + + /* + * The following require either SEC_FILE_READ_ATTRIBUTE or + * SEC_FILE_READ_EA. + */ + { LEVEL(RAW_FILEINFO_BASIC_INFORMATION, false, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_ALL_INFORMATION, false, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_NETWORK_OPEN_INFORMATION, false, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION, false, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_SMB2_ALL_INFORMATION, false, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_ATTRIBUTE) }, + { LEVEL(RAW_FILEINFO_SMB2_ALL_EAS, false, SEC_STD_SYNCHRONIZE, SEC_FILE_READ_EA) }, + /* Also try SEC_FILE_READ_ATTRIBUTE to show that it is the wrong one */ + { LEVEL(RAW_FILEINFO_SMB2_ALL_EAS, false, SEC_FILE_READ_ATTRIBUTE, SEC_FILE_READ_EA) }, + { LEVEL(RAW_FILEINFO_SEC_DESC, false, SEC_STD_SYNCHRONIZE, SEC_STD_READ_CONTROL) }, + /* Also try SEC_FILE_READ_ATTRIBUTE to show that it is the wrong one */ + { LEVEL(RAW_FILEINFO_SEC_DESC, false, SEC_FILE_READ_ATTRIBUTE, SEC_STD_READ_CONTROL) } +}; + + +/* + test fileinfo levels +*/ +static bool torture_smb2_getfinfo_access(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "torture_smb2_getfinfo_access"; + struct smb2_handle hfile; + NTSTATUS status; + bool ret = true; + int i; + + smb2_deltree(tree, fname); + + torture_setup_complex_file(tctx, tree, fname); + + for (i = 0; i < ARRAY_SIZE(file_levels_access); i++) { + union smb_fileinfo finfo; + NTSTATUS expected_status; + + /* + * First open with unrestricted_access, SEC_STD_SYNCHRONIZE for + * most tests, info levels with unrestricted=true should allow + * this. + */ + status = torture_smb2_testfile_access( + tree, fname, &hfile, file_levels_access[i].unrestricted_access); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Unable to open test file\n"); + + if (file_levels_access[i].level == RAW_FILEINFO_SEC_DESC) { + finfo.query_secdesc.in.secinfo_flags = 0x7; + } + if (file_levels_access[i].level == RAW_FILEINFO_SMB2_ALL_EAS) { + finfo.all_eas.in.continue_flags = + SMB2_CONTINUE_FLAG_RESTART; + } + + finfo.generic.level = file_levels_access[i].level; + finfo.generic.in.file.handle = hfile; + + if (file_levels_access[i].unrestricted) { + expected_status = NT_STATUS_OK; + } else { + expected_status = NT_STATUS_ACCESS_DENIED; + } + + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_equal_goto( + tctx, status, expected_status, ret, done, + talloc_asprintf(tctx, "Level %s failed\n", + file_levels_access[i].name)); + + smb2_util_close(tree, hfile); + + /* + * Now open with expected access, getinfo should work. + */ + status = torture_smb2_testfile_access( + tree, fname, &hfile, file_levels_access[i].required_access); + torture_assert_ntstatus_ok_goto( + tctx, status, ret, done, + "Unable to open test file\n"); + + if (file_levels_access[i].level == RAW_FILEINFO_SEC_DESC) { + finfo.query_secdesc.in.secinfo_flags = 0x7; + } + if (file_levels_access[i].level == RAW_FILEINFO_SMB2_ALL_EAS) { + finfo.all_eas.in.continue_flags = + SMB2_CONTINUE_FLAG_RESTART; + } + finfo.generic.level = file_levels_access[i].level; + finfo.generic.in.file.handle = hfile; + + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto( + tctx, status, ret, done, + talloc_asprintf(tctx, "%s on file", + file_levels_access[i].name)); + + smb2_util_close(tree, hfile); + } + +done: + smb2_deltree(tree, fname); + return ret; +} + +struct torture_suite *torture_smb2_getinfo_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create( + ctx, "getinfo"); + + torture_suite_add_simple_test(suite, "complex", torture_smb2_getinfo); + torture_suite_add_simple_test(suite, "fsinfo", torture_smb2_fsinfo); + torture_suite_add_simple_test(suite, "qfs_buffercheck", + torture_smb2_qfs_buffercheck); + torture_suite_add_simple_test(suite, "qfile_buffercheck", + torture_smb2_qfile_buffercheck); + torture_suite_add_simple_test(suite, "qsec_buffercheck", + torture_smb2_qsec_buffercheck); + torture_suite_add_simple_test(suite, "granted", + torture_smb2_fileinfo_grant_read); + torture_suite_add_simple_test(suite, "normalized", + torture_smb2_fileinfo_normalized); + torture_suite_add_1smb2_test(suite, "getinfo_access", + torture_smb2_getfinfo_access); + return suite; +} diff --git a/source4/torture/smb2/ioctl.c b/source4/torture/smb2/ioctl.c new file mode 100644 index 0000000..6ceaccf --- /dev/null +++ b/source4/torture/smb2/ioctl.c @@ -0,0 +1,7549 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 ioctl operations + + Copyright (C) David Disseldorp 2011-2016 + + 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/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" +#include "librpc/gen_ndr/ndr_ioctl.h" +#include "lib/cmdline/cmdline.h" +#include "libcli/resolve/resolve.h" +#include "lib/param/param.h" +#include "lib/util/tevent_ntstatus.h" + +#define FNAME "testfsctl.dat" +#define FNAME2 "testfsctl2.dat" +#define DNAME "testfsctl_dir" + +/* + basic testing of SMB2 shadow copy calls +*/ +static bool test_ioctl_get_shadow_copy(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle h; + uint8_t buf[100]; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + torture_assert_ntstatus_ok(torture, status, "create write"); + + ZERO_ARRAY(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok(torture, status, "write"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_ENUM_SNAPS; + ioctl.smb2.in.max_output_response = 16; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) + || NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST)) { + torture_skip(torture, "FSCTL_SRV_ENUM_SNAPS not supported\n"); + } + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_ENUM_SNAPS"); + + return true; +} + +/* + basic testing of the SMB2 server side copy ioctls +*/ +static bool test_ioctl_req_resume_key(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle h; + uint8_t buf[100]; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct req_resume_key_rsp res_key; + enum ndr_err_code ndr_ret; + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + torture_assert_ntstatus_ok(torture, status, "create write"); + + ZERO_ARRAY(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok(torture, status, "write"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + ioctl.smb2.in.max_output_response = 32; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + NDR_PRINT_DEBUG(req_resume_key_rsp, &res_key); + + talloc_free(tmp_ctx); + return true; +} + +/* + testing fetching a resume key twice for one file handle +*/ +static bool test_ioctl_req_two_resume_keys(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle h; + uint8_t buf[100]; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct req_resume_key_rsp res_key; + enum ndr_err_code ndr_ret; + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + torture_assert_ntstatus_ok(torture, status, "create write"); + + ZERO_ARRAY(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok(torture, status, "write"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + ioctl.smb2.in.max_output_response = 32; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + NDR_PRINT_DEBUG(req_resume_key_rsp, &res_key); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h; + ioctl.smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + ioctl.smb2.in.max_output_response = 32; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + NDR_PRINT_DEBUG(req_resume_key_rsp, &res_key); + + talloc_free(tmp_ctx); + return true; +} + +static uint64_t patt_hash(uint64_t off) +{ + return off; +} + +static bool write_pattern(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_handle h, uint64_t off, uint64_t len, + uint64_t patt_off) +{ + NTSTATUS status; + uint64_t i; + uint8_t *buf; + uint64_t io_sz = MIN(1024 * 64, len); + + if (len == 0) { + return true; + } + + torture_assert(torture, (len % 8) == 0, "invalid write len"); + + buf = talloc_zero_size(mem_ctx, io_sz); + torture_assert(torture, (buf != NULL), "no memory for file data buf"); + + while (len > 0) { + for (i = 0; i <= io_sz - 8; i += 8) { + SBVAL(buf, i, patt_hash(patt_off)); + patt_off += 8; + } + + status = smb2_util_write(tree, h, + buf, off, io_sz); + torture_assert_ntstatus_ok(torture, status, "file write"); + + len -= io_sz; + off += io_sz; + } + + talloc_free(buf); + + return true; +} + +static bool check_pattern(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_handle h, uint64_t off, uint64_t len, + uint64_t patt_off) +{ + if (len == 0) { + return true; + } + + torture_assert(torture, (len % 8) == 0, "invalid read len"); + + while (len > 0) { + uint64_t i; + struct smb2_read r; + NTSTATUS status; + uint64_t io_sz = MIN(1024 * 64, len); + + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = io_sz; + r.in.offset = off; + status = smb2_read(tree, mem_ctx, &r); + torture_assert_ntstatus_ok(torture, status, "read"); + + torture_assert_u64_equal(torture, r.out.data.length, io_sz, + "read data len mismatch"); + + for (i = 0; i <= io_sz - 8; i += 8, patt_off += 8) { + uint64_t data = BVAL(r.out.data.data, i); + torture_assert_u64_equal(torture, data, patt_hash(patt_off), + talloc_asprintf(torture, "read data " + "pattern bad at %llu\n", + (unsigned long long)off + i)); + } + talloc_free(r.out.data.data); + len -= io_sz; + off += io_sz; + } + + return true; +} + +static bool check_zero(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + struct smb2_handle h, uint64_t off, uint64_t len) +{ + uint64_t i; + struct smb2_read r; + NTSTATUS status; + + if (len == 0) { + return true; + } + + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = len; + r.in.offset = off; + status = smb2_read(tree, mem_ctx, &r); + torture_assert_ntstatus_ok(torture, status, "read"); + + torture_assert_u64_equal(torture, r.out.data.length, len, + "read data len mismatch"); + + for (i = 0; i <= len - 8; i += 8) { + uint64_t data = BVAL(r.out.data.data, i); + torture_assert_u64_equal(torture, data, 0, + talloc_asprintf(mem_ctx, "read zero " + "bad at %llu\n", + (unsigned long long)i)); + } + + talloc_free(r.out.data.data); + return true; +} + +static bool test_setup_open(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + const char *fname, + struct smb2_handle *fh, + uint32_t desired_access, + uint32_t file_attributes) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.desired_access = desired_access; + io.in.file_attributes = file_attributes; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + if (file_attributes & FILE_ATTRIBUTE_DIRECTORY) { + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + } + io.in.fname = fname; + + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "file create"); + + *fh = io.out.file.handle; + + return true; +} + +static bool test_setup_create_fill(struct torture_context *torture, + struct smb2_tree *tree, TALLOC_CTX *mem_ctx, + const char *fname, + struct smb2_handle *fh, + uint64_t size, + uint32_t desired_access, + uint32_t file_attributes) +{ + bool ok; + uint32_t initial_access = desired_access; + + if (size > 0) { + initial_access |= SEC_FILE_APPEND_DATA; + } + + smb2_util_unlink(tree, fname); + + ok = test_setup_open(torture, tree, mem_ctx, + fname, + fh, + initial_access, + file_attributes); + torture_assert(torture, ok, "file create"); + + if (size > 0) { + ok = write_pattern(torture, tree, mem_ctx, *fh, 0, size, 0); + torture_assert(torture, ok, "write pattern"); + } + + if (initial_access != desired_access) { + smb2_util_close(tree, *fh); + ok = test_setup_open(torture, tree, mem_ctx, + fname, + fh, + desired_access, + file_attributes); + torture_assert(torture, ok, "file open"); + } + + return true; +} + +static bool test_setup_copy_chunk(struct torture_context *torture, + struct smb2_tree *src_tree, + struct smb2_tree *dst_tree, + TALLOC_CTX *mem_ctx, + uint32_t nchunks, + const char *src_name, + struct smb2_handle *src_h, + uint64_t src_size, + uint32_t src_desired_access, + const char *dst_name, + struct smb2_handle *dest_h, + uint64_t dest_size, + uint32_t dest_desired_access, + struct srv_copychunk_copy *cc_copy, + union smb_ioctl *ioctl) +{ + struct req_resume_key_rsp res_key; + bool ok; + NTSTATUS status; + enum ndr_err_code ndr_ret; + + ok = test_setup_create_fill(torture, src_tree, mem_ctx, src_name, + src_h, src_size, src_desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "src file create fill"); + + ok = test_setup_create_fill(torture, dst_tree, mem_ctx, dst_name, + dest_h, dest_size, dest_desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "dest file create fill"); + + ZERO_STRUCTPN(ioctl); + ioctl->smb2.level = RAW_IOCTL_SMB2; + ioctl->smb2.in.file.handle = *src_h; + ioctl->smb2.in.function = FSCTL_SRV_REQUEST_RESUME_KEY; + /* Allow for Key + ContextLength + Context */ + ioctl->smb2.in.max_output_response = 32; + ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(src_tree, mem_ctx, &ioctl->smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_REQUEST_RESUME_KEY"); + + ndr_ret = ndr_pull_struct_blob(&ioctl->smb2.out.out, mem_ctx, &res_key, + (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp); + + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_req_resume_key_rsp"); + + ZERO_STRUCTPN(ioctl); + ioctl->smb2.level = RAW_IOCTL_SMB2; + ioctl->smb2.in.file.handle = *dest_h; + ioctl->smb2.in.function = FSCTL_SRV_COPYCHUNK; + ioctl->smb2.in.max_output_response = sizeof(struct srv_copychunk_rsp); + ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + ZERO_STRUCTPN(cc_copy); + memcpy(cc_copy->source_key, res_key.resume_key, ARRAY_SIZE(cc_copy->source_key)); + cc_copy->chunk_count = nchunks; + cc_copy->chunks = talloc_zero_array(mem_ctx, struct srv_copychunk, nchunks); + torture_assert(torture, (cc_copy->chunks != NULL), "no memory for chunks"); + + return true; +} + + +static bool check_copy_chunk_rsp(struct torture_context *torture, + struct srv_copychunk_rsp *cc_rsp, + uint32_t ex_chunks_written, + uint32_t ex_chunk_bytes_written, + uint32_t ex_total_bytes_written) +{ + torture_assert_int_equal(torture, cc_rsp->chunks_written, + ex_chunks_written, "num chunks"); + torture_assert_int_equal(torture, cc_rsp->chunk_bytes_written, + ex_chunk_bytes_written, "chunk bytes written"); + torture_assert_int_equal(torture, cc_rsp->total_bytes_written, + ex_total_bytes_written, "chunk total bytes"); + return true; +} + +static bool test_ioctl_copy_chunk_simple(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_multi(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 8192, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data via two chunks */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + cc_copy.chunks[1].source_off = 4096; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_tiny(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 96, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data via two chunks, sub block size chunks */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 48; + + cc_copy.chunks[1].source_off = 48; + cc_copy.chunks[1].target_off = 48; + cc_copy.chunks[1].length = 48; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 96); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 96, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_over(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 8192, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 4096, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* first chunk overwrites existing dest data */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* second chunk overwrites the first */ + cc_copy.chunks[1].source_off = 4096; + cc_copy.chunks[1].target_off = 0; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 4096); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_append(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* second chunk appends the same data to the first */ + cc_copy.chunks[1].source_off = 0; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 4096, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_limits(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* send huge chunk length request */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = UINT_MAX; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, "marshalling request"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "bad oversize chunk response"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, "unmarshalling response"); + + torture_comment(torture, "limit max chunks, got %u\n", + cc_rsp.chunks_written); + torture_comment(torture, "limit max chunk len, got %u\n", + cc_rsp.chunk_bytes_written); + torture_comment(torture, "limit max total bytes, got %u\n", + cc_rsp.total_bytes_written); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_src_lck(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle src_h2; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* open and lock the copychunk src file */ + status = torture_smb2_testfile(tree, FNAME, &src_h2); + torture_assert_ntstatus_ok(torture, status, "2nd src open"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = src_h2; + lck.in.locks = el; + el[0].offset = cc_copy.chunks[0].source_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "lock"); + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + /* + * 2k12 & Samba return lock_conflict, Windows 7 & 2k8 return success... + * + * Edgar Olougouna @ MS wrote: + * Regarding the FSCTL_SRV_COPYCHUNK and STATUS_FILE_LOCK_CONFLICT + * discrepancy observed between Windows versions, we confirm that the + * behavior change is expected. + * + * CopyChunk in Windows Server 2012 use regular Readfile/Writefile APIs + * to move the chunks from the source to the destination. + * These ReadFile/WriteFile APIs go through the byte-range lock checks, + * and this explains the observed STATUS_FILE_LOCK_CONFLICT error. + * + * Prior to Windows Server 2012, CopyChunk used mapped sections to move + * the data. And byte range locks are not enforced on mapped I/O, and + * this explains the STATUS_SUCCESS observed on Windows Server 2008 R2. + */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_FILE_LOCK_CONFLICT, + "FSCTL_SRV_COPYCHUNK locked"); + + /* should get cc response data with the lock conflict status */ + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 0, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 0); /* total bytes written */ + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = src_h2; + lck.in.locks = el; + el[0].offset = cc_copy.chunks[0].source_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "unlock"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK unlocked"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h2); + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_dest_lck(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + struct smb2_handle dest_h2; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 4096, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 4096, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* open and lock the copychunk dest file */ + status = torture_smb2_testfile(tree, FNAME2, &dest_h2); + torture_assert_ntstatus_ok(torture, status, "2nd src open"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = dest_h2; + lck.in.locks = el; + el[0].offset = cc_copy.chunks[0].target_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "lock"); + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_FILE_LOCK_CONFLICT, + "FSCTL_SRV_COPYCHUNK locked"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = dest_h2; + lck.in.locks = el; + el[0].offset = cc_copy.chunks[0].target_off; + el[0].length = cc_copy.chunks[0].length; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "unlock"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK unlocked"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, dest_h2); + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_bad_key(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, + FNAME, + &src_h, 4096, + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* overwrite the resume key with a bogus value */ + memcpy(cc_copy.source_key, "deadbeefdeadbeefdeadbeef", 24); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + /* Server 2k12 returns NT_STATUS_OBJECT_NAME_NOT_FOUND */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_OBJECT_NAME_NOT_FOUND, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_src_is_dest(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, + FNAME, + &src_h, 8192, + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* the source is also the destination */ + ioctl.smb2.in.file.handle = src_h; + + /* non-overlapping */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 4096; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, src_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + ok = check_pattern(torture, tree, tmp_ctx, src_h, 4096, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +/* + * Test a single-chunk copychunk request, where the source and target ranges + * overlap, and the SourceKey refers to the same target file. E.g: + * + * Initial State + * ------------- + * File: src_and_dest + * Offset: 0123456789 + * Data: abcdefghij + * + * Request + * ------- + * FSCTL_SRV_COPYCHUNK(src_and_dest) + * SourceKey = SRV_REQUEST_RESUME_KEY(src_and_dest) + * ChunkCount = 1 + * Chunks[0].SourceOffset = 0 + * Chunks[0].TargetOffset = 4 + * Chunks[0].Length = 6 + * + * Resultant State + * --------------- + * File: src_and_dest + * Offset: 0123456789 + * Data: abcdabcdef + * + * The resultant contents of src_and_dest is dependent on the server's + * copy algorithm. In the above example, the server uses an IO buffer + * large enough to hold the entire six-byte source data before writing + * to TargetOffset. If the server were to use a four-byte IO buffer and + * started reads/writes from the lowest offset, then the two overlapping + * bytes in the above example would be overwritten before being read. The + * resultant file contents would be abcdabcdab. + * + * Windows 2008r2 appears to use a 2048 byte copy buffer, overlapping bytes + * after this offset are written before being read. Windows 2012 on the + * other hand appears to use a buffer large enough to hold its maximum + * supported chunk size (1M). Samba currently uses a 64k copy buffer by + * default (vfs_cc_state.buf). + * + * This test uses an 8-byte overlap at 2040-2048, so that it passes against + * Windows 2008r2, 2012 and Samba servers. Note, 2008GM fails, as it appears + * to use a different copy algorithm to 2008r2. + */ +static bool +test_ioctl_copy_chunk_src_is_dest_overlap(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + /* exceed the vfs_default copy buffer */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, + FNAME, + &src_h, 2048 * 2, + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* the source is also the destination */ + ioctl.smb2.in.file.handle = src_h; + + /* 8 bytes overlap between source and target ranges */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 2048 - 8; + cc_copy.chunks[0].length = 2048; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 2048); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, src_h, 0, 2048 - 8, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + ok = check_pattern(torture, tree, tmp_ctx, src_h, 2048 - 8, 2048, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_bad_access(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + /* read permission on src */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE, + FNAME2, &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, tmp_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_OK, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* execute permission on src */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_EXECUTE | SEC_FILE_READ_ATTRIBUTE, + FNAME2, &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, tmp_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_OK, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* neither read nor execute permission on src */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_ATTRIBUTE, FNAME2, &dest_h, + 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* no write permission on dest */ + ok = test_setup_copy_chunk( + torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE, FNAME2, &dest_h, + 0, /* 0 byte dest file */ + (SEC_RIGHTS_FILE_ALL & + ~(SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA)), + &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* no read permission on dest */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, 1, /* 1 chunk */ + FNAME, &src_h, 4096, /* fill 4096 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE, + FNAME2, &dest_h, 0, /* 0 byte dest file */ + (SEC_RIGHTS_FILE_ALL & ~SEC_FILE_READ_DATA), + &cc_copy, &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + /* + * FSCTL_SRV_COPYCHUNK requires read permission on dest, + * FSCTL_SRV_COPYCHUNK_WRITE on the other hand does not. + */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + + return true; +} + +static bool test_ioctl_copy_chunk_write_access(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + + /* no read permission on dest with FSCTL_SRV_COPYCHUNK_WRITE */ + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + (SEC_RIGHTS_FILE_WRITE + | SEC_RIGHTS_FILE_EXECUTE), + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + ioctl.smb2.in.function = FSCTL_SRV_COPYCHUNK_WRITE; + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK_WRITE"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + + return true; +} + +static bool test_ioctl_copy_chunk_src_exceed(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* Request copy where off + length exceeds size of src */ + cc_copy.chunks[0].source_off = 1024; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_VIEW_SIZE, + "FSCTL_SRV_COPYCHUNK oversize"); + + /* Request copy where length exceeds size of src */ + cc_copy.chunks[0].source_off = 1024; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 3072; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SRV_COPYCHUNK just right"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 3072); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 3072, 1024); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool +test_ioctl_copy_chunk_src_exceed_multi(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 2, /* 2 chunks */ + FNAME, + &src_h, 8192, /* fill 8192 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* Request copy where off + length exceeds size of src */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + cc_copy.chunks[1].source_off = 4096; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 8192; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_VIEW_SIZE, + "FSCTL_SRV_COPYCHUNK oversize"); + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, "unmarshalling response"); + + /* first chunk should still be written */ + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_sparse_dest(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + struct smb2_read r; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + int i; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* copy all src file data (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 4096; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + if (!ok) { + torture_fail(torture, "bad copy chunk response data"); + } + + /* check for zeros in first 4k */ + ZERO_STRUCT(r); + r.in.file.handle = dest_h; + r.in.length = 4096; + r.in.offset = 0; + status = smb2_read(tree, tmp_ctx, &r); + torture_assert_ntstatus_ok(torture, status, "read"); + + torture_assert_u64_equal(torture, r.out.data.length, 4096, + "read data len mismatch"); + + for (i = 0; i < 4096; i++) { + torture_assert(torture, (r.out.data.data[i] == 0), + "sparse did not pass class"); + } + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 4096, 4096, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +/* + * set the ioctl MaxOutputResponse size to less than + * sizeof(struct srv_copychunk_rsp) + */ +static bool test_ioctl_copy_chunk_max_output_sz(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + /* req is valid, but use undersize max_output_response */ + ioctl.smb2.in.max_output_response = sizeof(struct srv_copychunk_rsp) - 1; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_SRV_COPYCHUNK"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_copy_chunk_zero_length(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + union smb_fileinfo q; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + if (!ok) { + torture_fail(torture, "setup copy chunk error"); + } + + /* zero length server-side copy (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 0; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "bad zero-length chunk response"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, "unmarshalling response"); + + ZERO_STRUCT(q); + q.all_info2.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + q.all_info2.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, torture, &q); + torture_assert_ntstatus_ok(torture, status, "getinfo"); + + torture_assert_int_equal(torture, q.all_info2.out.size, 0, + "size after zero len clone"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool copy_one_stream(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *tmp_ctx, + const char *src_sname, + const char *dst_sname) +{ + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + NTSTATUS status; + union smb_ioctl io; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + bool ok = false; + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* 1 chunk */ + src_sname, + &src_h, 256, /* fill 256 byte src file */ + SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, + dst_sname, + &dest_h, 0, /* 0 byte dest file */ + SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA, + &cc_copy, + &io); + torture_assert_goto(torture, ok == true, ok, done, + "setup copy chunk error\n"); + + /* copy all src file data (via a single chunk desc) */ + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 256; + + ndr_ret = ndr_push_struct_blob( + &io.smb2.in.out, tmp_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + + torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree, tmp_ctx, &io.smb2); + torture_assert_ntstatus_ok_goto(torture, status, ok, done, + "FSCTL_SRV_COPYCHUNK\n"); + + ndr_ret = ndr_pull_struct_blob( + &io.smb2.out.out, tmp_ctx, &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + + torture_assert_ndr_success_goto(torture, ndr_ret, ok, done, + "ndr_pull_srv_copychunk_rsp\n"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 256); /* total bytes written */ + torture_assert_goto(torture, ok == true, ok, done, + "bad copy chunk response data\n"); + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 256, 0); + if (!ok) { + torture_fail(torture, "inconsistent file data\n"); + } + +done: + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree, dest_h); + } + + return ok; +} + +/** + * Create a file + **/ +static bool torture_setup_file(TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + const char *name) +{ + struct smb2_create io; + NTSTATUS status; + + smb2_util_unlink(tree, name); + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = name; + + status = smb2_create(tree, mem_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + status = smb2_util_close(tree, io.out.file.handle); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + return true; +} + +static bool test_copy_chunk_streams(struct torture_context *torture, + struct smb2_tree *tree) +{ + const char *src_name = "src"; + const char *dst_name = "dst"; + struct names { + const char *src_sname; + const char *dst_sname; + } names[] = { + { "src:foo", "dst:foo" } + }; + int i; + TALLOC_CTX *tmp_ctx = NULL; + bool ok = false; + + tmp_ctx = talloc_new(tree); + torture_assert_not_null_goto(torture, tmp_ctx, ok, done, + "torture_setup_file\n"); + + ok = torture_setup_file(torture, tree, src_name); + torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n"); + ok = torture_setup_file(torture, tree, dst_name); + torture_assert_goto(torture, ok == true, ok, done, "torture_setup_file\n"); + + for (i = 0; i < ARRAY_SIZE(names); i++) { + ok = copy_one_stream(torture, tree, tmp_ctx, + names[i].src_sname, + names[i].dst_sname); + torture_assert_goto(torture, ok == true, ok, done, + "copy_one_stream failed\n"); + } + +done: + smb2_util_unlink(tree, src_name); + smb2_util_unlink(tree, dst_name); + talloc_free(tmp_ctx); + return ok; +} + +static bool test_copy_chunk_across_shares(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + NTSTATUS status; + bool ok = false; + + mem_ctx = talloc_new(tctx); + torture_assert_not_null_goto(tctx, mem_ctx, ok, done, + "talloc_new\n"); + + ok = torture_smb2_tree_connect(tctx, tree->session, tctx, &tree2); + torture_assert_goto(tctx, ok == true, ok, done, + "torture_smb2_tree_connect failed\n"); + + ok = test_setup_copy_chunk(tctx, tree, tree2, mem_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert_goto(tctx, ok == true, ok, done, + "test_setup_copy_chunk failed\n"); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, mem_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree2, mem_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "FSCTL_SRV_COPYCHUNK\n"); + + ndr_ret = ndr_pull_struct_blob( + &ioctl.smb2.out.out, mem_ctx, &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_pull_srv_copychunk_rsp\n"); + + ok = check_copy_chunk_rsp(tctx, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 4096); /* total bytes written */ + torture_assert_goto(tctx, ok == true, ok, done, + "bad copy chunk response data\n"); + + ok = check_pattern(tctx, tree2, mem_ctx, dest_h, 0, 4096, 0); + torture_assert_goto(tctx, ok == true, ok, done, + "inconsistent file data\n"); + +done: + TALLOC_FREE(mem_ctx); + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree2, dest_h); + } + smb2_util_unlink(tree, FNAME); + smb2_util_unlink(tree2, FNAME2); + if (tree2 != NULL) { + smb2_tdis(tree2); + } + return ok; +} + +/* Test closing the src handle */ +static bool test_copy_chunk_across_shares2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + enum ndr_err_code ndr_ret; + NTSTATUS status; + bool ok = false; + + mem_ctx = talloc_new(tctx); + torture_assert_not_null_goto(tctx, mem_ctx, ok, done, + "talloc_new\n"); + + ok = torture_smb2_tree_connect(tctx, tree->session, tctx, &tree2); + torture_assert_goto(tctx, ok == true, ok, done, + "torture_smb2_tree_connect failed\n"); + + ok = test_setup_copy_chunk(tctx, tree, tree2, mem_ctx, + 1, /* 1 chunk */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert_goto(tctx, ok == true, ok, done, + "test_setup_copy_chunk failed\n"); + + status = smb2_util_close(tree, src_h); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(src_h); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, mem_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree2, mem_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ok, done, "smb2_ioctl failed\n"); + +done: + TALLOC_FREE(mem_ctx); + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree2, dest_h); + } + smb2_util_unlink(tree, FNAME); + smb2_util_unlink(tree2, FNAME2); + if (tree2 != NULL) { + smb2_tdis(tree2); + } + return ok; +} + +/* Test offset works */ +static bool test_copy_chunk_across_shares3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_handle src_h = {{0}}; + struct smb2_handle dest_h = {{0}}; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + NTSTATUS status; + bool ok = false; + + mem_ctx = talloc_new(tctx); + torture_assert_not_null_goto(tctx, mem_ctx, ok, done, + "talloc_new\n"); + + ok = torture_smb2_tree_connect(tctx, tree->session, tctx, &tree2); + torture_assert_goto(tctx, ok == true, ok, done, + "torture_smb2_tree_connect failed\n"); + + ok = test_setup_copy_chunk(tctx, tree, tree2, mem_ctx, + 2, /* 2 chunks */ + FNAME, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert_goto(tctx, ok == true, ok, done, + "test_setup_copy_chunk failed\n"); + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = 4096; + + /* second chunk appends the same data to the first */ + cc_copy.chunks[1].source_off = 0; + cc_copy.chunks[1].target_off = 4096; + cc_copy.chunks[1].length = 4096; + + ndr_ret = ndr_push_struct_blob( + &ioctl.smb2.in.out, mem_ctx, &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_push_srv_copychunk_copy\n"); + + status = smb2_ioctl(tree2, mem_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "smb2_ioctl failed\n"); + + ndr_ret = ndr_pull_struct_blob( + &ioctl.smb2.out.out, mem_ctx, &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + + torture_assert_ndr_success_goto(tctx, ndr_ret, ok, done, + "ndr_pull_srv_copychunk_rsp\n"); + + ok = check_copy_chunk_rsp(tctx, &cc_rsp, + 2, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + 8192); /* total bytes written */ + torture_assert_goto(tctx, ok == true, ok, done, + "check_copy_chunk_rsp failed\n"); + + ok = check_pattern(tctx, tree2, mem_ctx, dest_h, 0, 4096, 0); + torture_assert_goto(tctx, ok == true, ok, done, + "check_pattern failed\n"); + + ok = check_pattern(tctx, tree2, mem_ctx, dest_h, 4096, 4096, 0); + torture_assert_goto(tctx, ok == true, ok, done, + "check_pattern failed\n"); + +done: + TALLOC_FREE(mem_ctx); + if (!smb2_util_handle_empty(src_h)) { + smb2_util_close(tree, src_h); + } + if (!smb2_util_handle_empty(dest_h)) { + smb2_util_close(tree2, dest_h); + } + smb2_util_unlink(tree, FNAME); + smb2_util_unlink(tree2, FNAME2); + if (tree2 != NULL) { + smb2_tdis(tree2); + } + return ok; +} + +static NTSTATUS test_ioctl_compress_fs_supported(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + struct smb2_handle *fh, + bool *compress_support) +{ + NTSTATUS status; + union smb_fsinfo info; + + ZERO_STRUCT(info); + info.generic.level = RAW_QFS_ATTRIBUTE_INFORMATION; + info.generic.handle = *fh; + status = smb2_getinfo_fs(tree, tree, &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (info.attribute_info.out.fs_attr & FILE_FILE_COMPRESSION) { + *compress_support = true; + } else { + *compress_support = false; + } + return NT_STATUS_OK; +} + +static NTSTATUS test_ioctl_compress_get(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + uint16_t *_compression_fmt) +{ + union smb_ioctl ioctl; + struct compression_state cmpr_state; + enum ndr_err_code ndr_ret; + NTSTATUS status; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_GET_COMPRESSION; + ioctl.smb2.in.max_output_response = sizeof(struct compression_state); + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, mem_ctx, + &cmpr_state, + (ndr_pull_flags_fn_t)ndr_pull_compression_state); + + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + *_compression_fmt = cmpr_state.format; + return NT_STATUS_OK; +} + +static NTSTATUS test_ioctl_compress_set(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + uint16_t compression_fmt) +{ + union smb_ioctl ioctl; + struct compression_state cmpr_state; + enum ndr_err_code ndr_ret; + NTSTATUS status; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_COMPRESSION; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + cmpr_state.format = compression_fmt; + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, mem_ctx, + &cmpr_state, + (ndr_push_flags_fn_t)ndr_push_compression_state); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2); + return status; +} + +static bool test_ioctl_compress_file_flag(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "invalid compression state after set"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_dir_inherit(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle dirh; + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + uint16_t compression_fmt; + bool ok; + char path_buf[PATH_MAX]; + + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup compression directory"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &dirh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + torture_skip(torture, "FS compression not supported\n"); + } + + /* set compression on parent dir, then check for inheritance */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, dirh, + COMPRESSION_FORMAT_LZNT1); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "invalid compression state after set"); + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + path_buf, &fh, 4096, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "compression attr not inherited by new file"); + + /* check compressed data is consistent */ + ok = check_pattern(torture, tree, tmp_ctx, fh, 0, 4096, 0); + + /* disable dir compression attr, file should remain compressed */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, dirh, + COMPRESSION_FORMAT_NONE); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "file compression attr removed after dir change"); + smb2_util_close(tree, fh); + + /* new files should no longer inherit compression attr */ + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME2); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + path_buf, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression attr present on new file"); + + smb2_util_close(tree, fh); + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_invalid_format(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + 0x0042); /* bogus */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "invalid FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_invalid_buf(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + union smb_ioctl ioctl; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_GET_COMPRESSION; + ioctl.smb2.in.max_output_response = 0; /* no room for rsp data */ + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_USER_BUFFER) + && !NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + /* neither Server 2k12 nor 2k8r2 response status */ + torture_assert(torture, true, + "invalid FSCTL_SET_COMPRESSION"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_query_file_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.all_info2.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr before set"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + (io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED), + "no compression attr after set"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + * Specify FILE_ATTRIBUTE_COMPRESSED on creation, Windows does not retain this + * attribute. + */ +static bool test_ioctl_compress_create_with_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh2; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + uint16_t compression_fmt; + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME2, &fh2, 0, SEC_RIGHTS_FILE_ALL, + (FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_COMPRESSED)); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh2, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh2); + torture_skip(torture, "FS compression not supported\n"); + } + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh2, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh2; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.all_info2.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "incorrect compression attr"); + + smb2_util_close(tree, fh2); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_inherit_disable(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + struct smb2_handle dirh; + char path_buf[PATH_MAX]; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + struct smb2_create io; + + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup compression directory"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &dirh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + torture_skip(torture, "FS compression not supported\n"); + } + + /* set compression on parent dir, then check for inheritance */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, dirh, + COMPRESSION_FORMAT_LZNT1); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "invalid compression state after set"); + smb2_util_close(tree, dirh); + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + path_buf, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_LZNT1), + "compression attr not inherited by new file"); + smb2_util_close(tree, fh); + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, FNAME2); + + /* NO_COMPRESSION option should block inheritance */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = NTCREATEX_OPTIONS_NO_COMPRESSION; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.fname = path_buf; + + status = smb2_create(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "file create"); + + fh = io.out.file.handle; + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression attr inherited by NO_COMPRESSION file"); + smb2_util_close(tree, fh); + + + snprintf(path_buf, PATH_MAX, "%s\\%s", DNAME, DNAME); + ZERO_STRUCT(io); + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.create_options = (NTCREATEX_OPTIONS_NO_COMPRESSION + | NTCREATEX_OPTIONS_DIRECTORY); + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.fname = path_buf; + + status = smb2_create(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "dir create"); + + dirh = io.out.file.handle; + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression attr inherited by NO_COMPRESSION dir"); + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + + talloc_free(tmp_ctx); + return true; +} + +/* attempting to set compression via SetInfo should not stick */ +static bool test_ioctl_compress_set_file_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + struct smb2_handle dirh; + union smb_fileinfo io; + union smb_setfileinfo set_io; + uint16_t compression_fmt; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr before set"); + + ZERO_STRUCT(set_io); + set_io.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + set_io.basic_info.in.file.handle = fh; + set_io.basic_info.in.create_time = io.basic_info.out.create_time; + set_io.basic_info.in.access_time = io.basic_info.out.access_time; + set_io.basic_info.in.write_time = io.basic_info.out.write_time; + set_io.basic_info.in.change_time = io.basic_info.out.change_time; + set_io.basic_info.in.attrib = (io.basic_info.out.attrib + | FILE_ATTRIBUTE_COMPRESSED); + status = smb2_setinfo_file(tree, &set_io); + torture_assert_ntstatus_ok(torture, status, "SMB2_SETINFO_FILE"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr after set"); + + smb2_util_close(tree, fh); + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup compression directory"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = dirh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.basic_info.out.attrib & FILE_ATTRIBUTE_COMPRESSED) == 0), + "compression attr before set"); + + ZERO_STRUCT(set_io); + set_io.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + set_io.basic_info.in.file.handle = dirh; + set_io.basic_info.in.create_time = io.basic_info.out.create_time; + set_io.basic_info.in.access_time = io.basic_info.out.access_time; + set_io.basic_info.in.write_time = io.basic_info.out.write_time; + set_io.basic_info.in.change_time = io.basic_info.out.change_time; + set_io.basic_info.in.attrib = (io.basic_info.out.attrib + | FILE_ATTRIBUTE_COMPRESSED); + status = smb2_setinfo_file(tree, &set_io); + torture_assert_ntstatus_ok(torture, status, "SMB2_SETINFO_FILE"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, dirh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "dir compression set after SetInfo"); + + smb2_util_close(tree, dirh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_perms(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + uint16_t compression_fmt; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + smb2_util_close(tree, fh); + if (!ok) { + torture_skip(torture, "FS compression not supported\n"); + } + + /* attempt get compression without READ_ATTR permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_READ & ~(SEC_FILE_READ_ATTRIBUTE + | SEC_STD_READ_CONTROL + | SEC_FILE_READ_EA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression set after create"); + smb2_util_close(tree, fh); + + /* set compression without WRITE_ATTR permission should succeed */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & ~(SEC_FILE_WRITE_ATTRIBUTE + | SEC_STD_WRITE_DAC + | SEC_FILE_WRITE_EA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + (io.all_info2.out.attrib & FILE_ATTRIBUTE_COMPRESSED), + "incorrect compression attr"); + smb2_util_close(tree, fh); + + /* attempt get compression without READ_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_READ & ~SEC_FILE_READ_DATA), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression enabled after set"); + smb2_util_close(tree, fh); + + /* attempt get compression with only SYNCHRONIZE permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_STD_SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "compression not enabled after set"); + smb2_util_close(tree, fh); + + /* attempt to set compression without WRITE_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & (~SEC_FILE_WRITE_DATA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SET_COMPRESSION permission"); + smb2_util_close(tree, fh); + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & (~SEC_FILE_WRITE_DATA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_NONE); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SET_COMPRESSION permission"); + smb2_util_close(tree, fh); + + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_notsup_get(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint16_t compression_fmt; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + /* skip if the server DOES support compression */ + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression supported\n"); + } + + /* + * Despite not supporting compression, we should get a successful + * response indicating that the file is uncompressed - like WS2016. + */ + status = test_ioctl_compress_get(torture, tmp_ctx, tree, fh, + &compression_fmt); + torture_assert_ntstatus_ok(torture, status, "FSCTL_GET_COMPRESSION"); + + torture_assert(torture, (compression_fmt == COMPRESSION_FORMAT_NONE), + "initial compression state not NONE"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_compress_notsup_set(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup compression file"); + + /* skip if the server DOES support compression */ + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression supported\n"); + } + + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_NOT_SUPPORTED, + "FSCTL_SET_COMPRESSION default"); + + /* + * Despite not supporting compression, we should get a successful + * response for set(COMPRESSION_FORMAT_NONE) - like WS2016 ReFS. + */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_NONE); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_SET_COMPRESSION none"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + basic testing of the SMB2 FSCTL_QUERY_NETWORK_INTERFACE_INFO ioctl +*/ +static bool test_ioctl_network_interface_info(struct torture_context *torture, + struct smb2_tree *tree) +{ + union smb_ioctl ioctl; + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_net_iface_info net_iface; + enum ndr_err_code ndr_ret; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(torture, "server doesn't support SMB2_CAP_MULTI_CHANNEL\n"); + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + fh.data[0] = UINT64_MAX; + fh.data[1] = UINT64_MAX; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_NETWORK_INTERFACE_INFO; + ioctl.smb2.in.max_output_response = 0x10000; /* Windows client sets this to 64KiB */ + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, &net_iface, + (ndr_pull_flags_fn_t)ndr_pull_fsctl_net_iface_info); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_fsctl_net_iface_info"); + + NDR_PRINT_DEBUG(fsctl_net_iface_info, &net_iface); + + talloc_free(tmp_ctx); + return true; +} + +/* + * Check whether all @fs_support_flags are set in the server's + * RAW_QFS_ATTRIBUTE_INFORMATION FileSystemAttributes response. + */ +static NTSTATUS test_ioctl_fs_supported(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + struct smb2_handle *fh, + uint64_t fs_support_flags, + bool *supported) +{ + NTSTATUS status; + union smb_fsinfo info; + + ZERO_STRUCT(info); + info.generic.level = RAW_QFS_ATTRIBUTE_INFORMATION; + info.generic.handle = *fh; + status = smb2_getinfo_fs(tree, tree, &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ((info.attribute_info.out.fs_attr & fs_support_flags) + == fs_support_flags) { + *supported = true; + } else { + *supported = false; + } + return NT_STATUS_OK; +} + +static NTSTATUS test_ioctl_sparse_req(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + bool set) +{ + union smb_ioctl ioctl; + NTSTATUS status; + uint8_t set_sparse; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + set_sparse = (set ? 0xFF : 0x0); + ioctl.smb2.in.out.data = &set_sparse; + ioctl.smb2.in.out.length = sizeof(set_sparse); + + status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2); + return status; +} + +static NTSTATUS test_sparse_get(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + bool *_is_sparse) +{ + union smb_fileinfo io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, mem_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *_is_sparse = !!(io.basic_info.out.attrib & FILE_ATTRIBUTE_SPARSE); + + return status; +} + +/* + * Manually test setting and clearing sparse flag. Intended for file system + * specifc tests to toggle the flag through SMB and check the status in the + * file system. + */ +bool test_ioctl_set_sparse(struct torture_context *tctx) +{ + bool set, ret = true; + const char *filename = NULL; + struct smb2_create create = { }; + struct smb2_tree *tree = NULL; + NTSTATUS status; + + set = torture_setting_bool(tctx, "set_sparse", true); + filename = torture_setting_string(tctx, "filename", NULL); + + if (filename == NULL) { + torture_fail(tctx, "Need to provide filename through " + "--option=torture:filename=testfile\n"); + return false; + } + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = 0; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.fname = filename; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE failed.\n"); + + status = test_ioctl_sparse_req(tctx, tctx, tree, + create.out.file.handle, set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "FSCTL_SET_SPARSE failed.\n"); +done: + + return ret; +} + +static bool test_ioctl_sparse_file_flag(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_fileinfo io; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FILE"); + + torture_assert(torture, + ((io.all_info2.out.attrib & FILE_ATTRIBUTE_SPARSE) == 0), + "sparse attr before set"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr after unset"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_file_attr(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + (FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_SPARSE)); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr on open"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_dir_flag(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle dirh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + smb2_deltree(tree, DNAME); + ok = test_setup_create_fill(torture, tree, tmp_ctx, + DNAME, &dirh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_DIRECTORY); + torture_assert(torture, ok, "setup sparse directory"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &dirh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* set sparse dir should fail, check for 2k12 & 2k8 response */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, dirh, true); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "dir FSCTL_SET_SPARSE status"); + + smb2_util_close(tree, dirh); + smb2_deltree(tree, DNAME); + talloc_free(tmp_ctx); + return true; +} + +/* + * FSCTL_SET_SPARSE can be sent with (already tested) or without a SetSparse + * buffer to indicate whether the flag should be set or cleared. When sent + * without a buffer, it must be handled as if SetSparse=TRUE. + */ +static bool test_ioctl_sparse_set_nobuf(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + /* ioctl.smb2.in.out is zeroed, no SetSparse buffer */ + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + /* second non-SetSparse request shouldn't toggle sparse */ + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after 2nd set"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr after unset"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_set_oversize(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + uint8_t buf[100]; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + /* + * Attach a request buffer larger than FILE_SET_SPARSE_BUFFER + * Windows still successfully processes the request. + */ + ZERO_ARRAY(buf); + buf[0] = 0xFF; /* attempt to set sparse */ + ioctl.smb2.in.out.data = buf; + ioctl.smb2.in.out.length = ARRAY_SIZE(buf); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_SPARSE; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + ZERO_ARRAY(buf); /* clear sparse */ + ioctl.smb2.in.out.data = buf; + ioctl.smb2.in.out.length = ARRAY_SIZE(buf); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr after clear"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static NTSTATUS test_ioctl_qar_req(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + int64_t req_off, + int64_t req_len, + struct file_alloced_range_buf **_rsp, + uint64_t *_rsp_count) +{ + union smb_ioctl ioctl; + NTSTATUS status; + enum ndr_err_code ndr_ret; + struct file_alloced_range_buf far_buf; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + int i; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_ALLOCATED_RANGES; + ioctl.smb2.in.max_output_response = 1024; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + far_buf.file_off = req_off; + far_buf.len = req_len; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &far_buf, + (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf); + if (ndr_ret != NDR_ERR_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + if (ioctl.smb2.out.out.length == 0) { + goto done; + } + + if ((ioctl.smb2.out.out.length % sizeof(far_buf)) != 0) { + torture_comment(torture, "invalid qry_alloced rsp len: %zd:", + ioctl.smb2.out.out.length); + status = NT_STATUS_INVALID_VIEW_SIZE; + goto err_out; + } + + far_count = (ioctl.smb2.out.out.length / sizeof(far_buf)); + far_rsp = talloc_array(mem_ctx, struct file_alloced_range_buf, + far_count); + if (far_rsp == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + for (i = 0; i < far_count; i++) { + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &far_rsp[i], + (ndr_pull_flags_fn_t)ndr_pull_file_alloced_range_buf); + if (ndr_ret != NDR_ERR_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + /* move to next buffer */ + ioctl.smb2.out.out.data += sizeof(far_buf); + ioctl.smb2.out.out.length -= sizeof(far_buf); + } + +done: + *_rsp = far_rsp; + *_rsp_count = far_count; + status = NT_STATUS_OK; +err_out: + talloc_free(tmp_ctx); + return status; +} + +static bool test_ioctl_sparse_qar(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + /* zero length file, shouldn't have any ranges */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 0, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "no sparse attr after set"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + /* write into the (now) sparse file at 4k offset */ + ok = write_pattern(torture, tree, tmp_ctx, fh, + 4096, /* off */ + 1024, /* len */ + 4096); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* + * Query range before write off. Whether it's allocated or not is FS + * dependent. NTFS deallocates chunks in 64K increments, but others + * (e.g. XFS, Btrfs, etc.) may deallocate 4K chunks. + */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 0) { + torture_comment(torture, "FS deallocated 4K chunk\n"); + } else { + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, "far offset"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, "far len"); + } + + /* + * Query range before and past write, it should be allocated up to the + * end of the write. + */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 8192, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + /* FS dependent */ + if (far_rsp[0].file_off == 4096) { + /* 4K chunk unallocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 4096, "far offset"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, "far len"); + } else { + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, "far offset"); + torture_assert_u64_equal(torture, far_rsp[0].len, 5120, "far len"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_qar_malformed(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + struct file_alloced_range_buf far_buf; + NTSTATUS status; + enum ndr_err_code ndr_ret; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + size_t old_len; + + /* zero length file, shouldn't have any ranges */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* no allocated ranges, no space for range response, should pass */ + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_ALLOCATED_RANGES; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + far_buf.file_off = 0; + far_buf.len = 1024; + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &far_buf, + (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf); + torture_assert_ndr_success(torture, ndr_ret, "push far ndr buf"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_QUERY_ALLOCATED_RANGES"); + + /* write into the file at 4k offset */ + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + 1024, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* allocated range, no space for range response, should fail */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_BUFFER_TOO_SMALL, "qar no space"); + + /* oversize (2x) file_alloced_range_buf in request, should pass */ + ioctl.smb2.in.max_output_response = 1024; + old_len = ioctl.smb2.in.out.length; + ok = data_blob_realloc(tmp_ctx, &ioctl.smb2.in.out, + (ioctl.smb2.in.out.length * 2)); + torture_assert(torture, ok, "2x data buffer"); + memcpy(ioctl.smb2.in.out.data + old_len, ioctl.smb2.in.out.data, + old_len); + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "qar too big"); + + /* no file_alloced_range_buf in request, should fail */ + data_blob_free(&ioctl.smb2.in.out); + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, "qar empty"); + + return true; +} + +bool test_ioctl_alternate_data_stream(struct torture_context *tctx) +{ + bool ret = false; + const char *fname = DNAME "\\test_stream_ioctl_dir"; + const char *sname = DNAME "\\test_stream_ioctl_dir:stream"; + NTSTATUS status; + struct smb2_create create = {}; + struct smb2_tree *tree = NULL; + struct smb2_handle h1 = {{0}}; + union smb_ioctl ioctl; + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_HIDDEN, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + h1 = create.out.file.handle; + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + + create = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = sname, + }; + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = create.out.file.handle; + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = h1; + ioctl.smb2.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID, + ioctl.smb2.in.max_output_response = 64; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + status = smb2_ioctl(tree, tctx, &ioctl.smb2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_ioctl failed\n"); + ret = true; + +done: + + smb2_util_close(tree, h1); + smb2_deltree(tree, DNAME); + return ret; +} + +/* + * 2.3.57 FSCTL_SET_ZERO_DATA Request + * + * How an implementation zeros data within a file is implementation-dependent. + * A file system MAY choose to deallocate regions of disk space that have been + * zeroed.<50> + * <50> + * ... NTFS might deallocate disk space in the file if the file is stored on an + * NTFS volume, and the file is sparse or compressed. It will free any allocated + * space in chunks of 64 kilobytes that begin at an offset that is a multiple of + * 64 kilobytes. Other bytes in the file (prior to the first freed 64-kilobyte + * chunk and after the last freed 64-kilobyte chunk) will be zeroed but not + * deallocated. + */ +static NTSTATUS test_ioctl_zdata_req(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + int64_t off, + int64_t beyond_final_zero) +{ + union smb_ioctl ioctl; + NTSTATUS status; + enum ndr_err_code ndr_ret; + struct file_zero_data_info zdata_info; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_SET_ZERO_DATA; + ioctl.smb2.in.max_output_response = 0; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + zdata_info.file_off = off; + zdata_info.beyond_final_zero = beyond_final_zero; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &zdata_info, + (ndr_push_flags_fn_t)ndr_push_file_zero_data_info); + if (ndr_ret != NDR_ERR_SUCCESS) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = NT_STATUS_OK; +err_out: + talloc_free(tmp_ctx); + return status; +} + +bool test_ioctl_zero_data(struct torture_context *tctx) +{ + bool ret = true; + int offset, beyond_final_zero; + const char *filename; + NTSTATUS status; + struct smb2_create create = { }; + struct smb2_tree *tree = NULL; + + offset = torture_setting_int(tctx, "offset", -1); + + if (offset < 0) { + torture_fail(tctx, "Need to provide non-negative offset " + "through --option=torture:offset=NNN\n"); + return false; + } + + beyond_final_zero = torture_setting_int(tctx, "beyond_final_zero", + -1); + if (beyond_final_zero < 0) { + torture_fail(tctx, "Need to provide non-negative " + "'beyond final zero' through " + "--option=torture:beyond_final_zero=NNN\n"); + return false; + } + filename = torture_setting_string(tctx, "filename", NULL); + if (filename == NULL) { + torture_fail(tctx, "Need to provide filename through " + "--option=torture:filename=testfile\n"); + return false; + } + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = 0; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.fname = filename; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE failed.\n"); + + status = test_ioctl_zdata_req(tctx, tctx, tree, + create.out.file.handle, + offset, + beyond_final_zero); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "FSCTL_ZERO_DATA failed.\n"); + +done: + return ret; +} + +static bool test_ioctl_sparse_punch(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 4096, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + /* zero (hole-punch) the data, without sparse flag */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, + "unexpected far len"); + /* check that the data is now zeroed */ + ok = check_zero(torture, tree, tmp_ctx, fh, 0, 4096); + torture_assert(torture, ok, "non-sparse zeroed range"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* still fully allocated on NTFS, see note below for Samba */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + /* + * FS specific: Samba uses PUNCH_HOLE to zero the range, and + * subsequently uses fallocate() to allocate the punched range if the + * file is marked non-sparse and "strict allocate" is enabled. In both + * cases, the zeroed range will not be detected by SEEK_DATA, so the + * range won't be present in QAR responses until the file is marked + * non-sparse again. + */ + if (far_count == 0) { + torture_comment(torture, "non-sparse zeroed range disappeared " + "after marking sparse\n"); + } else { + /* NTFS: range remains fully allocated */ + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, + "unexpected far len"); + } + + /* zero (hole-punch) the data, _with_ sparse flag */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* the range should no longer be alloced */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + ok = check_zero(torture, tree, tmp_ctx, fh, 0, 4096); + torture_assert(torture, ok, "sparse zeroed range"); + + /* remove sparse flag, this should "unsparse" the zeroed range */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + /* expect fully allocated */ + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, 4096, + "unexpected far len"); + + ok = check_zero(torture, tree, tmp_ctx, fh, 0, 4096); + torture_assert(torture, ok, "sparse zeroed range"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + * Find the point at which a zeroed range in a sparse file is deallocated by the + * underlying filesystem. NTFS on Windows Server 2012 deallocates chunks in 64k + * increments. Also check whether zeroed neighbours are merged for deallocation. + */ +static bool test_ioctl_sparse_hole_dealloc(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t file_size; + uint64_t hlen; + uint64_t dealloc_chunk_len = 0; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file 1"); + + /* check for FS sparse file */ + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + file_size = 1024 * 1024; + + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + file_size, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* check allocated ranges, should be fully allocated */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, file_size, + "unexpected far len"); + + /* punch holes in sizes of 1k increments */ + for (hlen = 0; hlen <= file_size; hlen += 4096) { + + /* punch a hole from zero to the current increment */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + hlen); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* ensure hole is zeroed, and pattern is consistent */ + ok = check_zero(torture, tree, tmp_ctx, fh, 0, hlen); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, fh, hlen, + file_size - hlen, hlen); + torture_assert(torture, ok, "allocated pattern range"); + + /* Check allocated ranges, hole might have been deallocated */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES"); + if ((hlen == file_size) && (far_count == 0)) { + /* hole covered entire file, deallocation occurred */ + dealloc_chunk_len = file_size; + break; + } + + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off != 0) { + /* + * We now know the hole punch length needed to trigger a + * deallocation on this FS... + */ + dealloc_chunk_len = hlen; + torture_comment(torture, "hole punch %ju@0 resulted in " + "deallocation of %ju@0\n", + (uintmax_t)hlen, + (uintmax_t)far_rsp[0].file_off); + torture_assert_u64_equal(torture, + file_size - far_rsp[0].len, + far_rsp[0].file_off, + "invalid alloced range"); + break; + } + } + + if (dealloc_chunk_len == 0) { + torture_comment(torture, "strange, this FS never deallocates" + "zeroed ranges in sparse files\n"); + return true; /* FS specific, not a failure */ + } + + /* + * Check whether deallocation occurs when the (now known) + * deallocation chunk size is punched via two ZERO_DATA requests. + * I.e. Does the FS merge the two ranges and deallocate the chunk? + * NTFS on Windows Server 2012 does not. + */ + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + file_size, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* divide dealloc chunk size by two, to use as punch length */ + hlen = dealloc_chunk_len >> 1; + + /* + * /half of dealloc chunk size 1M\ + * | | + * /offset 0 | /dealloc chunk size | + * |------------------ |-------------------|-------------------| + * | zeroed, 1st punch | zeroed, 2nd punch | existing pattern | + */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + hlen); /* beyond final zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + hlen, /* off */ + dealloc_chunk_len); /* beyond final */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* ensure holes are zeroed, and pattern is consistent */ + ok = check_zero(torture, tree, tmp_ctx, fh, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, fh, dealloc_chunk_len, + file_size - dealloc_chunk_len, dealloc_chunk_len); + torture_assert(torture, ok, "allocated pattern range"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + + if ((far_count == 0) && (dealloc_chunk_len == file_size)) { + torture_comment(torture, "holes merged for deallocation of " + "full file\n"); + return true; + } + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off == dealloc_chunk_len) { + torture_comment(torture, "holes merged for deallocation of " + "%ju chunk\n", (uintmax_t)dealloc_chunk_len); + torture_assert_u64_equal(torture, + file_size - far_rsp[0].len, + far_rsp[0].file_off, + "invalid alloced range"); + } else { + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected deallocation"); + torture_comment(torture, "holes not merged for deallocation\n"); + } + + smb2_util_close(tree, fh); + + /* + * Check whether an unwritten range is allocated when a sparse file is + * written to at an offset past the dealloc chunk size: + * + * /dealloc chunk size + * /offset 0 | + * |------------------ |-------------------| + * | unwritten | pattern | + */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file 1"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + ok = write_pattern(torture, tree, tmp_ctx, fh, + dealloc_chunk_len, /* off */ + 1024, /* len */ + dealloc_chunk_len); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off == 0) { + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + torture_comment(torture, "unwritten range fully allocated\n"); + } else { + torture_assert_u64_equal(torture, far_rsp[0].file_off, dealloc_chunk_len, + "unexpected deallocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, + "unexpected far len"); + torture_comment(torture, "unwritten range not allocated\n"); + } + + ok = check_zero(torture, tree, tmp_ctx, fh, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, fh, dealloc_chunk_len, + 1024, dealloc_chunk_len); + torture_assert(torture, ok, "allocated pattern range"); + + /* unsparse, should now be fully allocated */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, false); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected deallocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* check whether a file with compression and sparse attrs can be deallocated */ +static bool test_ioctl_sparse_compressed(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t file_size = 1024 * 1024; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file 1"); + + /* check for FS sparse file and compression support */ + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_ioctl_compress_fs_supported(torture, tree, tmp_ctx, &fh, + &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "FS compression not supported\n"); + } + + /* set compression and write some data */ + status = test_ioctl_compress_set(torture, tmp_ctx, tree, fh, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_COMPRESSION"); + + ok = write_pattern(torture, tree, tmp_ctx, fh, + 0, /* off */ + file_size, /* len */ + 0); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* set sparse - now sparse and compressed */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* check allocated ranges, should be fully alloced */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, file_size, + "unexpected far len"); + + /* zero (hole-punch) all data, with sparse and compressed attrs */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* + * Windows Server 2012 still deallocates a zeroed range when a sparse + * file carries the compression attribute. + */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + file_size, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 0) { + torture_comment(torture, "sparse & compressed file " + "deallocated after hole-punch\n"); + } else { + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected far off"); + torture_assert_u64_equal(torture, far_rsp[0].len, file_size, + "unexpected far len"); + torture_comment(torture, "sparse & compressed file fully " + "allocated after hole-punch\n"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* + * Create a sparse file, then attempt to copy unallocated and allocated ranges + * into a target file using FSCTL_SRV_COPYCHUNK. + */ +static bool test_ioctl_sparse_copy_chunk(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + union smb_ioctl ioctl; + struct srv_copychunk_copy cc_copy; + struct srv_copychunk_rsp cc_rsp; + enum ndr_err_code ndr_ret; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &src_h, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + /* check for FS sparse file support */ + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + smb2_util_close(tree, src_h); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + } + + ok = test_setup_copy_chunk(torture, tree, tree, tmp_ctx, + 1, /* chunks */ + FNAME, + &src_h, 0, /* src file */ + SEC_RIGHTS_FILE_ALL, + FNAME2, + &dest_h, 0, /* dest file */ + SEC_RIGHTS_FILE_ALL, + &cc_copy, + &ioctl); + torture_assert(torture, ok, "setup copy chunk error"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, src_h, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* start after dealloc_chunk_len, to create an unwritten sparse range */ + ok = write_pattern(torture, tree, tmp_ctx, src_h, + dealloc_chunk_len, /* off */ + 1024, /* len */ + dealloc_chunk_len); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + /* Skip test if 64k chunk is allocated - FS specific */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, src_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + if (far_rsp[0].file_off == 0) { + torture_skip(torture, "unwritten range fully allocated\n"); + } + + torture_assert_u64_equal(torture, far_rsp[0].file_off, dealloc_chunk_len, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, + "unexpected far len"); + + /* copy-chunk unallocated + written ranges into non-sparse dest */ + + cc_copy.chunks[0].source_off = 0; + cc_copy.chunks[0].target_off = 0; + cc_copy.chunks[0].length = dealloc_chunk_len + 1024; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &cc_copy, + (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_srv_copychunk_copy"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + dealloc_chunk_len + 1024); /* bytes written */ + torture_assert(torture, ok, "bad copy chunk response data"); + + ok = check_zero(torture, tree, tmp_ctx, dest_h, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, dealloc_chunk_len, + 1024, dealloc_chunk_len); + torture_assert(torture, ok, "copychunked range"); + + /* copied range should be allocated in non-sparse dest */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + + /* set dest as sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, dest_h, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* zero (hole-punch) all data */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024); + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* zeroed range might be deallocated */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 0) { + /* FS specific (e.g. NTFS) */ + torture_comment(torture, "FS deallocates file on full-range " + "punch\n"); + } else { + /* FS specific (e.g. EXT4) */ + torture_comment(torture, "FS doesn't deallocate file on " + "full-range punch\n"); + } + ok = check_zero(torture, tree, tmp_ctx, dest_h, 0, + dealloc_chunk_len + 1024); + torture_assert(torture, ok, "punched zeroed range"); + + /* copy-chunk again, this time with sparse dest */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &cc_rsp, + (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_srv_copychunk_rsp"); + + ok = check_copy_chunk_rsp(torture, &cc_rsp, + 1, /* chunks written */ + 0, /* chunk bytes unsuccessfully written */ + dealloc_chunk_len + 1024); /* bytes written */ + torture_assert(torture, ok, "bad copy chunk response data"); + + ok = check_zero(torture, tree, tmp_ctx, dest_h, 0, dealloc_chunk_len); + torture_assert(torture, ok, "sparse zeroed range"); + + ok = check_pattern(torture, tree, tmp_ctx, dest_h, dealloc_chunk_len, + 1024, dealloc_chunk_len); + torture_assert(torture, ok, "copychunked range"); + + /* copied range may be allocated in sparse dest */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, dest_h, + 0, /* off */ + dealloc_chunk_len + 1024, /* len */ + &far_rsp, + &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + /* + * FS specific: sparse region may be unallocated in dest if copy-chunk + * is handled in a sparse preserving way - E.g. vfs_btrfs + * with BTRFS_IOC_CLONE_RANGE. + */ + if (far_rsp[0].file_off == dealloc_chunk_len) { + torture_comment(torture, "copy-chunk sparse range preserved\n"); + torture_assert_u64_equal(torture, far_rsp[0].len, 1024, + "unexpected far len"); + } else { + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len + 1024, + "unexpected far len"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_punch_invalid(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + int i; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 4096, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse attr before set"); + + /* loop twice, without and with sparse attrib */ + for (i = 0; i <= 1; i++) { + union smb_fileinfo io; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + /* get size before & after. zero data should never change it */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "getinfo"); + torture_assert_int_equal(torture, (int)io.all_info2.out.size, + 4096, "size after IO"); + + /* valid 8 byte zero data, but after EOF */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 4104); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* valid 8 byte zero data, but after EOF */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 8192, /* off */ + 8200); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(torture, status, "getinfo"); + torture_assert_int_equal(torture, (int)io.all_info2.out.size, + 4096, "size after IO"); + + /* valid 0 byte zero data, without sparse flag */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 4095, /* off */ + 4095); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* INVALID off is past beyond_final_zero */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 4095); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "invalid zero_data"); + + /* zero length QAR - valid */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 0, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + /* QAR after EOF - valid */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + /* set sparse */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, + true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_perms(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + smb2_util_close(tree, fh); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + } + + /* set sparse without WRITE_ATTR permission should succeed */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_WRITE & ~(SEC_FILE_WRITE_ATTRIBUTE + | SEC_STD_WRITE_DAC + | SEC_FILE_WRITE_EA)), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt get sparse without READ_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + (SEC_RIGHTS_FILE_READ & ~SEC_FILE_READ_DATA), + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse set"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only WRITE_ATTR permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_WRITE_ATTRIBUTE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only WRITE_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_WRITE_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only APPEND_DATA permission */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_APPEND_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt to set sparse with only WRITE_EA permission - should fail */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 0, + SEC_FILE_WRITE_EA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_SET_SPARSE permission"); + smb2_util_close(tree, fh); + + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, !is_sparse, "sparse after set"); + smb2_util_close(tree, fh); + + /* attempt QAR with only READ_ATTR permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_READ_ATTRIBUTE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_QUERY_ALLOCATED_RANGES req passed"); + smb2_util_close(tree, fh); + + /* attempt QAR with only READ_DATA permission */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_READ_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + smb2_util_close(tree, fh); + + /* attempt QAR with only READ_EA permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_READ_EA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 4096, /* off */ + 1024, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "FSCTL_QUERY_ALLOCATED_RANGES req passed"); + smb2_util_close(tree, fh); + + /* setup file for ZERO_DATA permissions tests */ + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 8192, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only WRITE_ATTR permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_WRITE_ATTRIBUTE, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "zero_data permission"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only WRITE_DATA permission */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_WRITE_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_ok(torture, status, "zero_data"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only APPEND_DATA permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_APPEND_DATA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "zero_data permission"); + smb2_util_close(tree, fh); + + /* attempt ZERO_DATA with only WRITE_EA permission - should fail */ + ok = test_setup_open(torture, tree, tmp_ctx, + FNAME, &fh, SEC_FILE_WRITE_EA, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_ACCESS_DENIED, + "zero_data permission"); + smb2_util_close(tree, fh); + + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_lck(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + struct smb2_handle fh2; + NTSTATUS status; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + bool is_sparse; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, FNAME, &fh, + dealloc_chunk_len, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + smb2_util_close(tree, fh); + } + + /* open and lock via separate fh2 */ + status = torture_smb2_testfile(tree, FNAME, &fh2); + torture_assert_ntstatus_ok(torture, status, "2nd src open"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = fh2; + lck.in.locks = el; + el[0].offset = 0; + el[0].length = dealloc_chunk_len; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "lock"); + + /* set sparse while locked */ + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + status = test_sparse_get(torture, tmp_ctx, tree, fh, &is_sparse); + torture_assert_ntstatus_ok(torture, status, "test_sparse_get"); + torture_assert(torture, is_sparse, "sparse attr after set"); + + /* zero data over locked range should fail */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096); /* beyond_final_zero */ + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_FILE_LOCK_CONFLICT, + "zero_data locked"); + + /* QAR over locked range should pass */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + 4096, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES locked"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + 4096, + "unexpected far len"); + + /* zero data over range past EOF should pass */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len, /* off */ + dealloc_chunk_len + 4096); + torture_assert_ntstatus_ok(torture, status, + "zero_data past EOF locked"); + + /* QAR over range past EOF should pass */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len, /* off */ + 4096, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES past EOF locked"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = fh2; + lck.in.locks = el; + el[0].offset = 0; + el[0].length = dealloc_chunk_len; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(torture, status, "unlock"); + + smb2_util_close(tree, fh2); + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* alleviate QAR off-by-one bug paranoia - help me ob1 */ +static bool test_ioctl_sparse_qar_ob1(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, dealloc_chunk_len * 2, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + smb2_util_close(tree, fh); + } + + /* non-sparse QAR with range one before EOF */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len * 2 - 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len * 2 - 1, + "unexpected far len"); + + /* non-sparse QAR with range one after EOF */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len * 2 + 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len * 2, + "unexpected far len"); + + /* non-sparse QAR with range one after EOF from off=1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 1, /* off */ + dealloc_chunk_len * 2, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 1, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len * 2 - 1, + "unexpected far len"); + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* punch out second chunk */ + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len, /* off */ + dealloc_chunk_len * 2); + torture_assert_ntstatus_ok(torture, status, "zero_data"); + + /* sparse QAR with range one before hole */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len - 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len - 1, + "unexpected far len"); + + /* sparse QAR with range one after hole */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, /* off */ + dealloc_chunk_len + 1, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 0, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len, + "unexpected far len"); + + /* sparse QAR with range one after hole from off=1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 1, /* off */ + dealloc_chunk_len, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, 1, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + dealloc_chunk_len - 1, + "unexpected far len"); + + /* sparse QAR with range one before EOF from off=chunk_len-1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len - 1, /* off */ + dealloc_chunk_len, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 1, + "unexpected response len"); + torture_assert_u64_equal(torture, far_rsp[0].file_off, + dealloc_chunk_len - 1, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[0].len, + 1, "unexpected far len"); + + /* sparse QAR with range one after EOF from off=chunk_len+1 */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + dealloc_chunk_len + 1, /* off */ + dealloc_chunk_len, /* len */ + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + torture_assert_u64_equal(torture, far_count, 0, + "unexpected response len"); + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +/* test QAR with multi-range responses */ +static bool test_ioctl_sparse_qar_multi(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + uint64_t dealloc_chunk_len = 64 * 1024; /* Windows 2012 */ + uint64_t this_off; + int i; + struct file_alloced_range_buf *far_rsp = NULL; + uint64_t far_count = 0; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, dealloc_chunk_len * 2, + SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + torture_skip(torture, "Sparse files not supported\n"); + smb2_util_close(tree, fh); + } + + status = test_ioctl_sparse_req(torture, tmp_ctx, tree, fh, true); + torture_assert_ntstatus_ok(torture, status, "FSCTL_SET_SPARSE"); + + /* each loop, write out two chunks and punch the first out */ + for (i = 0; i < 10; i++) { + this_off = i * dealloc_chunk_len * 2; + + ok = write_pattern(torture, tree, tmp_ctx, fh, + this_off, /* off */ + dealloc_chunk_len * 2, /* len */ + this_off); /* pattern offset */ + torture_assert(torture, ok, "write pattern"); + + status = test_ioctl_zdata_req(torture, tmp_ctx, tree, fh, + this_off, /* off */ + this_off + dealloc_chunk_len); + torture_assert_ntstatus_ok(torture, status, "zero_data"); + } + + /* should now have one separate region for each iteration */ + status = test_ioctl_qar_req(torture, tmp_ctx, tree, fh, + 0, + 10 * dealloc_chunk_len * 2, + &far_rsp, &far_count); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_ALLOCATED_RANGES req failed"); + if (far_count == 1) { + torture_comment(torture, "this FS doesn't deallocate 64K" + "zeroed ranges in sparse files\n"); + return true; /* FS specific, not a failure */ + } + torture_assert_u64_equal(torture, far_count, 10, + "unexpected response len"); + for (i = 0; i < 10; i++) { + this_off = i * dealloc_chunk_len * 2; + + torture_assert_u64_equal(torture, far_rsp[i].file_off, + this_off + dealloc_chunk_len, + "unexpected allocation"); + torture_assert_u64_equal(torture, far_rsp[i].len, + dealloc_chunk_len, + "unexpected far len"); + } + + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_sparse_qar_overflow(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + union smb_ioctl ioctl; + struct file_alloced_range_buf far_buf; + NTSTATUS status; + enum ndr_err_code ndr_ret; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + bool ok; + + ok = test_setup_create_fill(torture, tree, tmp_ctx, + FNAME, &fh, 1024, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "setup file"); + + status = test_ioctl_fs_supported(torture, tree, tmp_ctx, &fh, + FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(torture, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, fh); + torture_skip(torture, "Sparse files not supported\n"); + } + + /* no allocated ranges, no space for range response, should pass */ + ZERO_STRUCT(ioctl); + ioctl.smb2.level = RAW_IOCTL_SMB2; + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_ALLOCATED_RANGES; + ioctl.smb2.in.max_output_response = 1024; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + /* off + length wraps around to 511 */ + far_buf.file_off = 512; + far_buf.len = 0xffffffffffffffffLL; + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &far_buf, + (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf); + torture_assert_ndr_success(torture, ndr_ret, "push far ndr buf"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_ALLOCATED_RANGES overflow"); + + return true; +} + +static NTSTATUS test_ioctl_trim_supported(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + struct smb2_handle *fh, + bool *trim_support) +{ + NTSTATUS status; + union smb_fsinfo info; + + ZERO_STRUCT(info); + info.generic.level = RAW_QFS_SECTOR_SIZE_INFORMATION; + info.generic.handle = *fh; + status = smb2_getinfo_fs(tree, tree, &info); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS)) { + /* + * Windows < Server 2012, 8 etc. don't support this info level + * or the trim ioctl. Ignore the error and let the caller skip. + */ + *trim_support = false; + return NT_STATUS_OK; + } else if (!NT_STATUS_IS_OK(status)) { + return status; + } + + torture_comment(torture, "sector size info: lb/s=%u, pb/sA=%u, " + "pb/sP=%u, fse/sA=%u, flags=0x%x, bosa=%u, bopa=%u\n", + (unsigned)info.sector_size_info.out.logical_bytes_per_sector, + (unsigned)info.sector_size_info.out.phys_bytes_per_sector_atomic, + (unsigned)info.sector_size_info.out.phys_bytes_per_sector_perf, + (unsigned)info.sector_size_info.out.fs_effective_phys_bytes_per_sector_atomic, + (unsigned)info.sector_size_info.out.flags, + (unsigned)info.sector_size_info.out.byte_off_sector_align, + (unsigned)info.sector_size_info.out.byte_off_partition_align); + + if (info.sector_size_info.out.flags & QFS_SSINFO_FLAGS_TRIM_ENABLED) { + *trim_support = true; + } else { + *trim_support = false; + } + return NT_STATUS_OK; +} + +static bool test_setup_trim(struct torture_context *torture, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + uint32_t num_ranges, + struct smb2_handle *fh, + uint64_t file_size, + uint32_t desired_access, + struct fsctl_file_level_trim_req *trim_req, + union smb_ioctl *ioctl) +{ + bool ok; + + ok = test_setup_create_fill(torture, tree, mem_ctx, FNAME, + fh, file_size, desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(torture, ok, "src file create fill"); + + ZERO_STRUCTPN(ioctl); + ioctl->smb2.level = RAW_IOCTL_SMB2; + ioctl->smb2.in.file.handle = *fh; + ioctl->smb2.in.function = FSCTL_FILE_LEVEL_TRIM; + ioctl->smb2.in.max_output_response + = sizeof(struct fsctl_file_level_trim_rsp); + ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + ZERO_STRUCTPN(trim_req); + /* leave key as zero for now. TODO test locking with differing keys */ + trim_req->num_ranges = num_ranges; + trim_req->ranges = talloc_zero_array(mem_ctx, + struct file_level_trim_range, + num_ranges); + torture_assert(torture, (trim_req->ranges != NULL), "no memory for ranges"); + + return true; +} + +static bool test_ioctl_trim_simple(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct smb2_handle fh; + NTSTATUS status; + union smb_ioctl ioctl; + bool trim_supported; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_file_level_trim_req trim_req; + struct fsctl_file_level_trim_rsp trim_rsp; + uint64_t trim_chunk_len = 64 * 1024; /* trim 64K chunks */ + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_trim(torture, tree, tmp_ctx, + 1, /* 1 range */ + &fh, 2 * trim_chunk_len, /* fill 128K file */ + SEC_RIGHTS_FILE_ALL, + &trim_req, + &ioctl); + if (!ok) { + torture_fail(torture, "setup trim error"); + } + + status = test_ioctl_trim_supported(torture, tree, tmp_ctx, &fh, + &trim_supported); + torture_assert_ntstatus_ok(torture, status, "fsinfo"); + if (!trim_supported) { + smb2_util_close(tree, fh); + talloc_free(tmp_ctx); + torture_skip(torture, "trim not supported\n"); + } + + /* trim first chunk, leave second */ + trim_req.ranges[0].off = 0; + trim_req.ranges[0].len = trim_chunk_len; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, &trim_req, + (ndr_push_flags_fn_t)ndr_push_fsctl_file_level_trim_req); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_push_fsctl_file_level_trim_req"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(torture, status, "FILE_LEVEL_TRIM_RANGE"); + + ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx, + &trim_rsp, + (ndr_pull_flags_fn_t)ndr_pull_fsctl_file_level_trim_rsp); + torture_assert_ndr_success(torture, ndr_ret, + "ndr_pull_fsctl_file_level_trim_rsp"); + + torture_assert_int_equal(torture, trim_rsp.num_ranges_processed, 1, ""); + + /* second half of the file should remain consitent */ + ok = check_pattern(torture, tree, tmp_ctx, fh, trim_chunk_len, + trim_chunk_len, trim_chunk_len); + torture_assert(torture, ok, "non-trimmed range inconsistent"); + + return true; +} + +static bool test_setup_dup_extents(struct torture_context *tctx, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + struct smb2_handle *src_h, + uint64_t src_size, + uint32_t src_desired_access, + struct smb2_handle *dest_h, + uint64_t dest_size, + uint32_t dest_desired_access, + struct fsctl_dup_extents_to_file *dup_ext_buf, + union smb_ioctl *ioctl) +{ + bool ok; + + ok = test_setup_create_fill(tctx, tree, mem_ctx, FNAME, + src_h, src_size, src_desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(tctx, ok, "src file create fill"); + + ok = test_setup_create_fill(tctx, tree, mem_ctx, FNAME2, + dest_h, dest_size, dest_desired_access, + FILE_ATTRIBUTE_NORMAL); + torture_assert(tctx, ok, "dest file create fill"); + + ZERO_STRUCTPN(ioctl); + ioctl->smb2.level = RAW_IOCTL_SMB2; + ioctl->smb2.in.file.handle = *dest_h; + ioctl->smb2.in.function = FSCTL_DUP_EXTENTS_TO_FILE; + ioctl->smb2.in.max_output_response = 0; + ioctl->smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + ZERO_STRUCTPN(dup_ext_buf); + smb2_push_handle(dup_ext_buf->source_fid, src_h); + + return true; +} + +static bool test_ioctl_dup_extents_simple(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_fileinfo io; + union smb_setfileinfo sinfo; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + /* extend dest to match src len */ + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = + RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = dest_h; + sinfo.end_of_file_info.in.size = 4096; + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file"); + + /* copy all src file data */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + /* the file size shouldn't have been changed by this operation! */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 4096, "size after IO"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* reopen for pattern check */ + ok = test_setup_open(tctx, tree, tmp_ctx, FNAME, &src_h, + SEC_RIGHTS_FILE_ALL, FILE_ATTRIBUTE_NORMAL); + torture_assert_ntstatus_ok(tctx, status, "src open after dup"); + ok = test_setup_open(tctx, tree, tmp_ctx, FNAME2, &dest_h, + SEC_RIGHTS_FILE_ALL, FILE_ATTRIBUTE_NORMAL); + torture_assert_ntstatus_ok(tctx, status, "dest open after dup"); + + ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0); + if (!ok) { + torture_fail(tctx, "inconsistent src file data"); + } + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(tctx, "inconsistent dest file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_len_beyond_dest(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_fileinfo io; + union smb_setfileinfo sinfo; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 0, "size after IO"); + + /* copy all src file data */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 32768; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); +#if 0 + /* + * 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply - this should fail, but + * passes against WS2016 RTM! + */ + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED, + "FSCTL_DUP_EXTENTS_TO_FILE"); +#endif + + /* the file sizes shouldn't have been changed */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = src_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 32768, "size after IO"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 0, "size after IO"); + + /* extend dest */ + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = dest_h; + sinfo.end_of_file_info.in.size = 32768; + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file"); + + ok = check_zero(tctx, tree, tmp_ctx, dest_h, 0, 32768); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + /* reissue ioctl, now with enough space */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_len_beyond_src(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_fileinfo io; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 0, "size after IO"); + + /* exceed src file len */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 32768 * 2; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + /* the file sizes shouldn't have been changed */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = src_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 32768, "size after IO"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 0, "size after IO"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_len_zero(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_fileinfo io; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 0, "size after IO"); + + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 0; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE"); + + /* the file sizes shouldn't have been changed */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = src_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 32768, "size after IO"); + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = dest_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 0, "size after IO"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_sparse_src(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_setfileinfo sinfo; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 0, /* filled after sparse flag */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING + | FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, + "block refcounting and sparse files not supported\n"); + } + + /* set sparse flag on src */ + status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, src_h, true); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE"); + + ok = write_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0); + torture_assert(tctx, ok, "write pattern"); + + /* extend dest */ + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = dest_h; + sinfo.end_of_file_info.in.size = 4096; + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file"); + + /* copy all src file data */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + /* + * src is sparse, but spec says: 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE + * Reply... STATUS_NOT_SUPPORTED: Target file is sparse, while source + * is a non-sparse file. + */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_sparse_dest(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_setfileinfo sinfo; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 4096, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING + | FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, + "block refcounting and sparse files not supported\n"); + } + + /* set sparse flag on dest */ + status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, dest_h, true); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE"); + + /* extend dest */ + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = dest_h; + sinfo.end_of_file_info.in.size = dup_ext_buf.byte_count; + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file"); + + /* copy all src file data */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + /* + * dest is sparse, but spec says: 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE + * Reply... STATUS_NOT_SUPPORTED: Target file is sparse, while source + * is a non-sparse file. + */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_sparse_both(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_setfileinfo sinfo; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 0, /* fill 4096 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, /* 0 byte dest file */ + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING + | FILE_SUPPORTS_SPARSE_FILES, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, + "block refcounting and sparse files not supported\n"); + } + + /* set sparse flag on src and dest */ + status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, src_h, true); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE"); + status = test_ioctl_sparse_req(tctx, tmp_ctx, tree, dest_h, true); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_SPARSE"); + + ok = write_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0); + torture_assert(tctx, ok, "write pattern"); + + /* extend dest */ + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = dest_h; + sinfo.end_of_file_info.in.size = 4096; + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file"); + + /* copy all src file data */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE"); + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + + /* reopen for pattern check */ + ok = test_setup_open(tctx, tree, tmp_ctx, FNAME2, &dest_h, + SEC_RIGHTS_FILE_ALL, FILE_ATTRIBUTE_NORMAL); + torture_assert_ntstatus_ok(tctx, status, "dest open ater dup"); + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_src_is_dest(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_fileinfo io; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + /* dest_h not needed for this test */ + smb2_util_close(tree, dest_h); + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + /* src and dest are the same file handle */ + ioctl.smb2.in.file.handle = src_h; + + /* no overlap between src and tgt */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 16384; + dup_ext_buf.byte_count = 16384; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE"); + + /* the file size shouldn't have been changed */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = src_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 32768, "size after IO"); + + ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 16384, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + ok = check_pattern(tctx, tree, tmp_ctx, src_h, 16384, 16384, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + talloc_free(tmp_ctx); + return true; +} + +/* + * unlike copy-chunk, dup extents doesn't support overlapping ranges between + * source and target. This makes it a *lot* cleaner to implement on the server. + */ +static bool +test_ioctl_dup_extents_src_is_dest_overlap(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_fileinfo io; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + /* dest_h not needed for this test */ + smb2_util_close(tree, dest_h); + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + /* src and dest are the same file handle */ + ioctl.smb2.in.file.handle = src_h; + + /* 8K overlap between src and tgt */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 8192; + dup_ext_buf.byte_count = 16384; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_NOT_SUPPORTED, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + /* the file size and data should match beforehand */ + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = src_h; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "getinfo"); + torture_assert_int_equal(tctx, (int)io.all_info2.out.size, + 32768, "size after IO"); + + ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + talloc_free(tmp_ctx); + return true; +} + +/* + * The compression tests won't run against Windows servers yet - ReFS doesn't + * (yet) offer support for compression. + */ +static bool test_ioctl_dup_extents_compressed_src(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_setfileinfo sinfo; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 0, /* filled after compressed flag */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING + | FILE_FILE_COMPRESSION, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, + "block refcounting and compressed files not supported\n"); + } + + /* set compressed flag on src */ + status = test_ioctl_compress_set(tctx, tmp_ctx, tree, src_h, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_COMPRESSION"); + + ok = write_pattern(tctx, tree, tmp_ctx, src_h, 0, 4096, 0); + torture_assert(tctx, ok, "write pattern"); + + /* extend dest */ + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = dest_h; + sinfo.end_of_file_info.in.size = 4096; + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file"); + + /* copy all src file data */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_compressed_dest(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + union smb_setfileinfo sinfo; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 4096, + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING + | FILE_FILE_COMPRESSION, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, + "block refcounting and compressed files not supported\n"); + } + + /* set compressed flag on dest */ + status = test_ioctl_compress_set(tctx, tmp_ctx, tree, dest_h, + COMPRESSION_FORMAT_DEFAULT); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_SET_COMPRESSION"); + + /* extend dest */ + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = dest_h; + sinfo.end_of_file_info.in.size = 4096; + status = smb2_setinfo_file(tree, &sinfo); + torture_assert_ntstatus_ok(tctx, status, "smb2_setinfo_file"); + + /* copy all src file data */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 4096; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 4096, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_bad_handle(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + struct smb2_handle bogus_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + bool ok; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 32768, + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + /* open and close a file, keeping the handle as now a "bogus" handle */ + ok = test_setup_create_fill(tctx, tree, tmp_ctx, "bogus_file", + &bogus_h, 0, SEC_RIGHTS_FILE_ALL, + FILE_ATTRIBUTE_NORMAL); + torture_assert(tctx, ok, "bogus file create fill"); + smb2_util_close(tree, bogus_h); + + /* bogus dest file handle */ + ioctl.smb2.in.file.handle = bogus_h; + + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 32768; + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_FILE_CLOSED, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + /* reinstate dest, add bogus src file handle */ + ioctl.smb2.in.file.handle = dest_h; + smb2_push_handle(dup_ext_buf.source_fid, &bogus_h); + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_INVALID_HANDLE, + "FSCTL_DUP_EXTENTS_TO_FILE"); + + ok = check_pattern(tctx, tree, tmp_ctx, src_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_src_lck(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle src_h2; + struct smb2_handle dest_h; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + bool ok; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + /* dest pattern is different to src */ + ok = write_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 32768); + torture_assert(tctx, ok, "write pattern"); + + /* setup dup ext req, values used for locking */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 32768; + + /* open and lock the dup extents src file */ + status = torture_smb2_testfile(tree, FNAME, &src_h2); + torture_assert_ntstatus_ok(tctx, status, "2nd src open"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = src_h2; + lck.in.locks = el; + el[0].offset = dup_ext_buf.source_off; + el[0].length = dup_ext_buf.byte_count; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(tctx, status, "lock"); + + status = smb2_util_write(tree, src_h, + "conflicted", 0, sizeof("conflicted")); + torture_assert_ntstatus_equal(tctx, status, + NT_STATUS_FILE_LOCK_CONFLICT, "file write"); + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + /* + * In contrast to copy-chunk, dup extents doesn't cause a lock conflict + * here. + */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE"); + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = src_h2; + lck.in.locks = el; + el[0].offset = dup_ext_buf.source_off; + el[0].length = dup_ext_buf.byte_count; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(tctx, status, "unlock"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, + "FSCTL_DUP_EXTENTS_TO_FILE unlocked"); + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h2); + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + return true; +} + +static bool test_ioctl_dup_extents_dest_lck(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle src_h; + struct smb2_handle dest_h; + struct smb2_handle dest_h2; + NTSTATUS status; + union smb_ioctl ioctl; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + struct fsctl_dup_extents_to_file dup_ext_buf; + enum ndr_err_code ndr_ret; + bool ok; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + ok = test_setup_dup_extents(tctx, tree, tmp_ctx, + &src_h, 32768, /* fill 32768 byte src file */ + SEC_RIGHTS_FILE_ALL, + &dest_h, 0, + SEC_RIGHTS_FILE_ALL, + &dup_ext_buf, + &ioctl); + if (!ok) { + torture_fail(tctx, "setup dup extents error"); + } + + status = test_ioctl_fs_supported(tctx, tree, tmp_ctx, &src_h, + FILE_SUPPORTS_BLOCK_REFCOUNTING, &ok); + torture_assert_ntstatus_ok(tctx, status, "SMB2_GETINFO_FS"); + if (!ok) { + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + talloc_free(tmp_ctx); + torture_skip(tctx, "block refcounting not supported\n"); + } + + /* dest pattern is different to src */ + ok = write_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 32768); + torture_assert(tctx, ok, "write pattern"); + + /* setup dup ext req, values used for locking */ + dup_ext_buf.source_off = 0; + dup_ext_buf.target_off = 0; + dup_ext_buf.byte_count = 32768; + + /* open and lock the dup extents dest file */ + status = torture_smb2_testfile(tree, FNAME2, &dest_h2); + torture_assert_ntstatus_ok(tctx, status, "2nd src open"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = dest_h2; + lck.in.locks = el; + el[0].offset = dup_ext_buf.source_off; + el[0].length = dup_ext_buf.byte_count; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(tctx, status, "lock"); + + status = smb2_util_write(tree, dest_h, + "conflicted", 0, sizeof("conflicted")); + torture_assert_ntstatus_equal(tctx, status, + NT_STATUS_FILE_LOCK_CONFLICT, "file write"); + + ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx, + &dup_ext_buf, + (ndr_push_flags_fn_t)ndr_push_fsctl_dup_extents_to_file); + torture_assert_ndr_success(tctx, ndr_ret, + "ndr_push_fsctl_dup_extents_to_file"); + + /* + * In contrast to copy-chunk, dup extents doesn't cause a lock conflict + * here. + */ + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, "FSCTL_DUP_EXTENTS_TO_FILE"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000001; + lck.in.file.handle = dest_h2; + lck.in.locks = el; + el[0].offset = dup_ext_buf.source_off; + el[0].length = dup_ext_buf.byte_count; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok(tctx, status, "unlock"); + + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + torture_assert_ntstatus_ok(tctx, status, + "FSCTL_DUP_EXTENTS_TO_FILE unlocked"); + + ok = check_pattern(tctx, tree, tmp_ctx, dest_h, 0, 32768, 0); + if (!ok) { + torture_fail(tctx, "inconsistent file data"); + } + + smb2_util_close(tree, src_h); + smb2_util_close(tree, dest_h); + smb2_util_close(tree, dest_h2); + talloc_free(tmp_ctx); + return true; +} + +/* + basic regression test for BUG 14607 + https://bugzilla.samba.org/show_bug.cgi?id=14607 +*/ +static bool test_ioctl_bug14607(struct torture_context *torture, + struct smb2_tree *tree) +{ + TALLOC_CTX *tmp_ctx = talloc_new(tree); + uint32_t timeout_msec; + NTSTATUS status; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + status = smb2cli_ioctl(tree->session->transport->conn, + timeout_msec, + tree->session->smbXcli, + tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + tmp_ctx, + &out_input_buffer, + &out_output_buffer); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) || + NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) || + NT_STATUS_EQUAL(status, NT_STATUS_FS_DRIVER_REQUIRED) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST)) + { + torture_comment(torture, + "FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8: %s\n", + nt_errstr(status)); + torture_skip(torture, "server doesn't support FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8\n"); + } + torture_assert_ntstatus_ok(torture, status, "FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8"); + + torture_assert_int_equal(torture, out_output_buffer.length, 1, + "output length"); + torture_assert_int_equal(torture, out_output_buffer.data[0], 8, + "output buffer byte should be 8"); + + talloc_free(tmp_ctx); + return true; +} + +/* + basic regression test for BUG 14769 + https://bugzilla.samba.org/show_bug.cgi?id=14769 +*/ +static bool test_ioctl_bug14769(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + const char *fname = "bug14769"; + bool ret = false; + struct smb2_handle h; + struct smb2_ioctl ioctl; + struct smb2_close cl; + struct smb2_request *smb2arr[2] = { 0 }; + uint8_t tosend_msec = 200; + DATA_BLOB send_buf = { &tosend_msec, 1 }; + + /* Create a test file. */ + smb2_util_unlink(tree, fname); + status = torture_smb2_testfile(tree, fname, &h); + torture_assert_ntstatus_ok(torture, status, "create bug14769"); + + /* + * Send (not receive) the FSCTL. + * This should go async with a wait time of 200 msec. + */ + ZERO_STRUCT(ioctl); + ioctl.in.file.handle = h; + ioctl.in.function = FSCTL_SMBTORTURE_FSP_ASYNC_SLEEP; + ioctl.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + ioctl.in.out = send_buf; + + smb2arr[0] = smb2_ioctl_send(tree, &ioctl); + torture_assert_goto(torture, + smb2arr[0] != NULL, + ret, + done, + "smb2_ioctl_send failed\n"); + /* Immediately send the close. */ + ZERO_STRUCT(cl); + cl.in.file.handle = h; + cl.in.flags = 0; + smb2arr[1] = smb2_close_send(tree, &cl); + torture_assert_goto(torture, + smb2arr[1] != NULL, + ret, + done, + "smb2_close_send failed\n"); + + /* Now get the FSCTL reply. */ + /* + * If we suffer from bug #14769 this will fail as + * the ioctl will return with NT_STATUS_FILE_CLOSED, + * as the close will have closed the handle without + * waiting for the ioctl to complete. The server shouldn't + * complete the close until the ioctl finishes. + */ + status = smb2_ioctl_recv(smb2arr[0], tree, &ioctl); + torture_assert_ntstatus_ok_goto(torture, + status, + ret, + done, + "smb2_ioctl_recv failed\n"); + + /* Followed by the close reply. */ + status = smb2_close_recv(smb2arr[1], &cl); + torture_assert_ntstatus_ok_goto(torture, + status, + ret, + done, + "smb2_ioctl_close failed\n"); + ret = true; + + done: + smb2_util_unlink(tree, fname); + return ret; +} + +/* + basic regression test for BUG 14788, + with FSCTL_VALIDATE_NEGOTIATE_INFO + https://bugzilla.samba.org/show_bug.cgi?id=14788 +*/ +static bool test_ioctl_bug14788_VALIDATE_NEGOTIATE(struct torture_context *torture, + struct smb2_tree *tree0) +{ + const char *host = torture_setting_string(torture, "host", NULL); + const char *share = torture_setting_string(torture, "share", NULL); + const char *noperm_share = torture_setting_string(torture, "noperm_share", "noperm"); + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options; + struct smb2_transport *transport = NULL; + struct smb2_tree *tree = NULL; + struct smb2_session *session = NULL; + uint16_t noperm_flags = 0; + const char *noperm_unc = NULL; + struct smb2_tree *noperm_tree = NULL; + uint32_t timeout_msec; + struct tevent_req *subreq = NULL; + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(torture, "Can't test without SMB 3 support"); + } + + options = transport0->options; + options.client_guid = GUID_random(); + options.min_protocol = PROTOCOL_SMB3_00; + options.max_protocol = PROTOCOL_SMB3_02; + + status = smb2_connect(torture, + host, + lpcfg_smb_ports(torture->lp_ctx), + share, + lpcfg_resolve_context(torture->lp_ctx), + credentials, + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok(torture, status, "smb2_connect options failed"); + session = tree->session; + transport = session->transport; + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + subreq = smb2cli_validate_negotiate_info_send(torture, + torture->ev, + transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli); + torture_assert(torture, + tevent_req_poll_ntstatus(subreq, torture->ev, &status), + "tevent_req_poll_ntstatus"); + status = smb2cli_validate_negotiate_info_recv(subreq); + torture_assert_ntstatus_ok(torture, status, "smb2cli_validate_negotiate_info"); + + noperm_unc = talloc_asprintf(torture, "\\\\%s\\%s", host, noperm_share); + torture_assert(torture, noperm_unc != NULL, "talloc_asprintf"); + + noperm_tree = smb2_tree_init(session, torture, false); + torture_assert(torture, noperm_tree != NULL, "smb2_tree_init"); + + status = smb2cli_raw_tcon(transport->conn, + SMB2_HDR_FLAG_SIGNED, + 0, /* clear_flags */ + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + noperm_flags, + noperm_unc); + if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_NETWORK_NAME)) { + torture_skip(torture, talloc_asprintf(torture, + "noperm_unc[%s] %s", + noperm_unc, nt_errstr(status))); + } + torture_assert_ntstatus_ok(torture, status, + talloc_asprintf(torture, + "smb2cli_tcon(%s)", + noperm_unc)); + + subreq = smb2cli_validate_negotiate_info_send(torture, + torture->ev, + transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli); + torture_assert(torture, + tevent_req_poll_ntstatus(subreq, torture->ev, &status), + "tevent_req_poll_ntstatus"); + status = smb2cli_validate_negotiate_info_recv(subreq); + torture_assert_ntstatus_ok(torture, status, "smb2cli_validate_negotiate_info noperm"); + + return true; +} + +/* + basic regression test for BUG 14788, + with FSCTL_QUERY_NETWORK_INTERFACE_INFO + https://bugzilla.samba.org/show_bug.cgi?id=14788 +*/ +static bool test_ioctl_bug14788_NETWORK_INTERFACE(struct torture_context *torture, + struct smb2_tree *tree0) +{ + const char *host = torture_setting_string(torture, "host", NULL); + const char *share = torture_setting_string(torture, "share", NULL); + const char *noperm_share = torture_setting_string(torture, "noperm_share", "noperm"); + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options; + struct smb2_transport *transport = NULL; + struct smb2_tree *tree = NULL; + struct smb2_session *session = NULL; + uint16_t noperm_flags = 0; + const char *noperm_unc = NULL; + struct smb2_tree *noperm_tree = NULL; + uint32_t timeout_msec; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(torture, "Can't test without SMB 3 support"); + } + + options = transport0->options; + options.client_guid = GUID_random(); + options.min_protocol = PROTOCOL_SMB3_00; + options.max_protocol = PROTOCOL_SMB3_02; + + status = smb2_connect(torture, + host, + lpcfg_smb_ports(torture->lp_ctx), + share, + lpcfg_resolve_context(torture->lp_ctx), + credentials, + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok(torture, status, "smb2_connect options failed"); + session = tree->session; + transport = session->transport; + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + /* + * A valid FSCTL_QUERY_NETWORK_INTERFACE_INFO + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* is being UINT64_MAX and + * in_max_output_length = 1. + * + * This demonstrates NT_STATUS_BUFFER_TOO_SMALL + * if the server is not able to return the + * whole response buffer to the client. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_BUFFER_TOO_SMALL, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX. + * + * This gives INVALID_PARAMETER instead + * of FILE_CLOSED. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX and + * in_max_output_length = 1. + * + * This proves INVALID_PARAMETER instead + * of BUFFER_TOO_SMALL. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + noperm_unc = talloc_asprintf(torture, "\\\\%s\\%s", host, noperm_share); + torture_assert(torture, noperm_unc != NULL, "talloc_asprintf"); + + noperm_tree = smb2_tree_init(session, torture, false); + torture_assert(torture, noperm_tree != NULL, "smb2_tree_init"); + + status = smb2cli_raw_tcon(transport->conn, + SMB2_HDR_FLAG_SIGNED, + 0, /* clear_flags */ + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + noperm_flags, + noperm_unc); + if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_NETWORK_NAME)) { + torture_skip(torture, talloc_asprintf(torture, + "noperm_unc[%s] %s", + noperm_unc, nt_errstr(status))); + } + torture_assert_ntstatus_ok(torture, status, + talloc_asprintf(torture, + "smb2cli_tcon(%s)", + noperm_unc)); + + /* + * A valid FSCTL_QUERY_NETWORK_INTERFACE_INFO + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_ok(torture, status, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* is being UINT64_MAX and + * in_max_output_length = 1. + * + * This demonstrates NT_STATUS_BUFFER_TOO_SMALL + * if the server is not able to return the + * whole response buffer to the client. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_BUFFER_TOO_SMALL, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX. + * + * This gives INVALID_PARAMETER instead + * of FILE_CLOSED. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + UINT16_MAX, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + /* + * An invalid FSCTL_QUERY_NETWORK_INTERFACE_INFO, + * with file_id_* not being UINT64_MAX and + * in_max_output_length = 1. + * + * This proves INVALID_PARAMETER instead + * of BUFFER_TOO_SMALL. + */ + status = smb2cli_ioctl(transport->conn, + timeout_msec, + session->smbXcli, + noperm_tree->smbXcli, + INT64_MAX, /* in_fid_persistent */ + INT64_MAX, /* in_fid_volatile */ + FSCTL_QUERY_NETWORK_INTERFACE_INFO, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + torture, + &out_input_buffer, + &out_output_buffer); + torture_assert_ntstatus_equal(torture, status, + NT_STATUS_INVALID_PARAMETER, + "FSCTL_QUERY_NETWORK_INTERFACE_INFO"); + + return true; +} + +/* + * testing of SMB2 ioctls + */ +struct torture_suite *torture_smb2_ioctl_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "ioctl"); + struct torture_suite *bug14788 = torture_suite_create(ctx, "bug14788"); + + torture_suite_add_1smb2_test(suite, "shadow_copy", + test_ioctl_get_shadow_copy); + torture_suite_add_1smb2_test(suite, "req_resume_key", + test_ioctl_req_resume_key); + torture_suite_add_1smb2_test(suite, "req_two_resume_keys", + test_ioctl_req_two_resume_keys); + torture_suite_add_1smb2_test(suite, "copy_chunk_simple", + test_ioctl_copy_chunk_simple); + torture_suite_add_1smb2_test(suite, "copy_chunk_multi", + test_ioctl_copy_chunk_multi); + torture_suite_add_1smb2_test(suite, "copy_chunk_tiny", + test_ioctl_copy_chunk_tiny); + torture_suite_add_1smb2_test(suite, "copy_chunk_overwrite", + test_ioctl_copy_chunk_over); + torture_suite_add_1smb2_test(suite, "copy_chunk_append", + test_ioctl_copy_chunk_append); + torture_suite_add_1smb2_test(suite, "copy_chunk_limits", + test_ioctl_copy_chunk_limits); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_lock", + test_ioctl_copy_chunk_src_lck); + torture_suite_add_1smb2_test(suite, "copy_chunk_dest_lock", + test_ioctl_copy_chunk_dest_lck); + torture_suite_add_1smb2_test(suite, "copy_chunk_bad_key", + test_ioctl_copy_chunk_bad_key); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_is_dest", + test_ioctl_copy_chunk_src_is_dest); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_is_dest_overlap", + test_ioctl_copy_chunk_src_is_dest_overlap); + torture_suite_add_1smb2_test(suite, "copy_chunk_bad_access", + test_ioctl_copy_chunk_bad_access); + torture_suite_add_1smb2_test(suite, "copy_chunk_write_access", + test_ioctl_copy_chunk_write_access); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_exceed", + test_ioctl_copy_chunk_src_exceed); + torture_suite_add_1smb2_test(suite, "copy_chunk_src_exceed_multi", + test_ioctl_copy_chunk_src_exceed_multi); + torture_suite_add_1smb2_test(suite, "copy_chunk_sparse_dest", + test_ioctl_copy_chunk_sparse_dest); + torture_suite_add_1smb2_test(suite, "copy_chunk_max_output_sz", + test_ioctl_copy_chunk_max_output_sz); + torture_suite_add_1smb2_test(suite, "copy_chunk_zero_length", + test_ioctl_copy_chunk_zero_length); + torture_suite_add_1smb2_test(suite, "copy-chunk streams", + test_copy_chunk_streams); + torture_suite_add_1smb2_test(suite, "copy_chunk_across_shares", + test_copy_chunk_across_shares); + torture_suite_add_1smb2_test(suite, "copy_chunk_across_shares2", + test_copy_chunk_across_shares2); + torture_suite_add_1smb2_test(suite, "copy_chunk_across_shares3", + test_copy_chunk_across_shares3); + torture_suite_add_1smb2_test(suite, "compress_file_flag", + test_ioctl_compress_file_flag); + torture_suite_add_1smb2_test(suite, "compress_dir_inherit", + test_ioctl_compress_dir_inherit); + torture_suite_add_1smb2_test(suite, "compress_invalid_format", + test_ioctl_compress_invalid_format); + torture_suite_add_1smb2_test(suite, "compress_invalid_buf", + test_ioctl_compress_invalid_buf); + torture_suite_add_1smb2_test(suite, "compress_query_file_attr", + test_ioctl_compress_query_file_attr); + torture_suite_add_1smb2_test(suite, "compress_create_with_attr", + test_ioctl_compress_create_with_attr); + torture_suite_add_1smb2_test(suite, "compress_inherit_disable", + test_ioctl_compress_inherit_disable); + torture_suite_add_1smb2_test(suite, "compress_set_file_attr", + test_ioctl_compress_set_file_attr); + torture_suite_add_1smb2_test(suite, "compress_perms", + test_ioctl_compress_perms); + torture_suite_add_1smb2_test(suite, "compress_notsup_get", + test_ioctl_compress_notsup_get); + torture_suite_add_1smb2_test(suite, "compress_notsup_set", + test_ioctl_compress_notsup_set); + torture_suite_add_1smb2_test(suite, "network_interface_info", + test_ioctl_network_interface_info); + torture_suite_add_1smb2_test(suite, "sparse_file_flag", + test_ioctl_sparse_file_flag); + torture_suite_add_1smb2_test(suite, "sparse_file_attr", + test_ioctl_sparse_file_attr); + torture_suite_add_1smb2_test(suite, "sparse_dir_flag", + test_ioctl_sparse_dir_flag); + torture_suite_add_1smb2_test(suite, "sparse_set_nobuf", + test_ioctl_sparse_set_nobuf); + torture_suite_add_1smb2_test(suite, "sparse_set_oversize", + test_ioctl_sparse_set_oversize); + torture_suite_add_1smb2_test(suite, "sparse_qar", + test_ioctl_sparse_qar); + torture_suite_add_1smb2_test(suite, "sparse_qar_malformed", + test_ioctl_sparse_qar_malformed); + torture_suite_add_1smb2_test(suite, "sparse_punch", + test_ioctl_sparse_punch); + torture_suite_add_1smb2_test(suite, "sparse_hole_dealloc", + test_ioctl_sparse_hole_dealloc); + torture_suite_add_1smb2_test(suite, "sparse_compressed", + test_ioctl_sparse_compressed); + torture_suite_add_1smb2_test(suite, "sparse_copy_chunk", + test_ioctl_sparse_copy_chunk); + torture_suite_add_1smb2_test(suite, "sparse_punch_invalid", + test_ioctl_sparse_punch_invalid); + torture_suite_add_1smb2_test(suite, "sparse_perms", + test_ioctl_sparse_perms); + torture_suite_add_1smb2_test(suite, "sparse_lock", + test_ioctl_sparse_lck); + torture_suite_add_1smb2_test(suite, "sparse_qar_ob1", + test_ioctl_sparse_qar_ob1); + torture_suite_add_1smb2_test(suite, "sparse_qar_multi", + test_ioctl_sparse_qar_multi); + torture_suite_add_1smb2_test(suite, "sparse_qar_overflow", + test_ioctl_sparse_qar_overflow); + torture_suite_add_1smb2_test(suite, "trim_simple", + test_ioctl_trim_simple); + torture_suite_add_1smb2_test(suite, "dup_extents_simple", + test_ioctl_dup_extents_simple); + torture_suite_add_1smb2_test(suite, "dup_extents_len_beyond_dest", + test_ioctl_dup_extents_len_beyond_dest); + torture_suite_add_1smb2_test(suite, "dup_extents_len_beyond_src", + test_ioctl_dup_extents_len_beyond_src); + torture_suite_add_1smb2_test(suite, "dup_extents_len_zero", + test_ioctl_dup_extents_len_zero); + torture_suite_add_1smb2_test(suite, "dup_extents_sparse_src", + test_ioctl_dup_extents_sparse_src); + torture_suite_add_1smb2_test(suite, "dup_extents_sparse_dest", + test_ioctl_dup_extents_sparse_dest); + torture_suite_add_1smb2_test(suite, "dup_extents_sparse_both", + test_ioctl_dup_extents_sparse_both); + torture_suite_add_1smb2_test(suite, "dup_extents_src_is_dest", + test_ioctl_dup_extents_src_is_dest); + torture_suite_add_1smb2_test(suite, "dup_extents_src_is_dest_overlap", + test_ioctl_dup_extents_src_is_dest_overlap); + torture_suite_add_1smb2_test(suite, "dup_extents_compressed_src", + test_ioctl_dup_extents_compressed_src); + torture_suite_add_1smb2_test(suite, "dup_extents_compressed_dest", + test_ioctl_dup_extents_compressed_dest); + torture_suite_add_1smb2_test(suite, "dup_extents_bad_handle", + test_ioctl_dup_extents_bad_handle); + torture_suite_add_1smb2_test(suite, "dup_extents_src_lock", + test_ioctl_dup_extents_src_lck); + torture_suite_add_1smb2_test(suite, "dup_extents_dest_lock", + test_ioctl_dup_extents_dest_lck); + torture_suite_add_1smb2_test(suite, "bug14607", + test_ioctl_bug14607); + torture_suite_add_1smb2_test(suite, "bug14769", + test_ioctl_bug14769); + + torture_suite_add_1smb2_test(bug14788, "VALIDATE_NEGOTIATE", + test_ioctl_bug14788_VALIDATE_NEGOTIATE); + torture_suite_add_1smb2_test(bug14788, "NETWORK_INTERFACE", + test_ioctl_bug14788_NETWORK_INTERFACE); + torture_suite_add_suite(suite, bug14788); + + suite->description = talloc_strdup(suite, "SMB2-IOCTL tests"); + + return suite; +} + diff --git a/source4/torture/smb2/lease.c b/source4/torture/smb2/lease.c new file mode 100644 index 0000000..a2c354d --- /dev/null +++ b/source4/torture/smb2/lease.c @@ -0,0 +1,4819 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 leases + + Copyright (C) Zachary Loafman 2009 + + 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/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/util.h" +#include "libcli/smb/smbXcli_base.h" +#include "libcli/security/security.h" +#include "lib/param/param.h" +#include "lease_break_handler.h" + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \ + __location__, #v, (int)(v), (int)(correct)); \ + ret = false; \ + }} while (0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ + nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_CREATED(__io, __created, __attribute) \ + do { \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ + CHECK_VAL((__io)->out.size, 0); \ + CHECK_VAL((__io)->out.file_attr, (__attribute)); \ + CHECK_VAL((__io)->out.reserved2, 0); \ + } while(0) + +#define CHECK_LEASE(__io, __state, __oplevel, __key, __flags) \ + do { \ + CHECK_VAL((__io)->out.lease_response.lease_version, 1); \ + if (__oplevel) { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[0], (__key)); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__io)->out.lease_response.lease_state, smb2_util_lease_state(__state)); \ + } else { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[0], 0); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[1], 0); \ + CHECK_VAL((__io)->out.lease_response.lease_state, 0); \ + } \ + \ + CHECK_VAL((__io)->out.lease_response.lease_flags, (__flags)); \ + CHECK_VAL((__io)->out.lease_response.lease_duration, 0); \ + CHECK_VAL((__io)->out.lease_response.lease_epoch, 0); \ + } while(0) + +#define CHECK_LEASE_V2(__io, __state, __oplevel, __key, __flags, __parent, __epoch) \ + do { \ + CHECK_VAL((__io)->out.lease_response_v2.lease_version, 2); \ + if (__oplevel) { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], (__key)); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_state, smb2_util_lease_state(__state)); \ + } else { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_state, 0); \ + } \ + \ + CHECK_VAL((__io)->out.lease_response_v2.lease_flags, __flags); \ + if (__flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET) { \ + CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[0], (__parent)); \ + CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[1], ~(__parent)); \ + } \ + CHECK_VAL((__io)->out.lease_response_v2.lease_duration, 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_epoch, (__epoch)); \ + } while(0) + +static const uint64_t LEASE1 = 0xBADC0FFEE0DDF00Dull; +static const uint64_t LEASE2 = 0xDEADBEEFFEEDBEADull; +static const uint64_t LEASE3 = 0xDAD0FFEDD00DF00Dull; +static const uint64_t LEASE4 = 0xBAD0FFEDD00DF00Dull; + +#define NREQUEST_RESULTS 8 +static const char *request_results[NREQUEST_RESULTS][2] = { + { "", "" }, + { "R", "R" }, + { "H", "" }, + { "W", "" }, + { "RH", "RH" }, + { "RW", "RW" }, + { "HW", "" }, + { "RHW", "RHW" }, +}; + +static bool test_lease_request(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + const char *fname = "lease_request.dat"; + const char *fname2 = "lease_request.2.dat"; + const char *sname = "lease_request.dat:stream"; + const char *dname = "lease_request.dir"; + bool ret = true; + int i; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname2); + smb2_util_rmdir(tree, dname); + + /* Win7 is happy to grant RHW leases on files. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1, 0); + + /* But will reject leases on directories. */ + if (!(caps & SMB2_CAP_DIRECTORY_LEASING)) { + smb2_lease_create(&io, &ls, true, dname, LEASE2, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_DIRECTORY); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + smb2_util_close(tree, io.out.file.handle); + } + + /* Also rejects multiple files leased under the same key. */ + smb2_lease_create(&io, &ls, true, fname2, LEASE1, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* And grants leases on streams (with separate leasekey). */ + smb2_lease_create(&io, &ls, false, sname, LEASE2, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + h2 = io.out.file.handle; + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE2, 0); + smb2_util_close(tree, h2); + + smb2_util_close(tree, h1); + + /* Now see what combos are actually granted. */ + for (i = 0; i < NREQUEST_RESULTS; i++) { + torture_comment(tctx, "Requesting lease type %s(%x)," + " expecting %s(%x)\n", + request_results[i][0], smb2_util_lease_state(request_results[i][0]), + request_results[i][1], smb2_util_lease_state(request_results[i][1])); + smb2_lease_create(&io, &ls, false, fname, LEASE1, + smb2_util_lease_state(request_results[i][0])); + status = smb2_create(tree, mem_ctx, &io); + h2 = io.out.file.handle; + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, request_results[i][1], true, LEASE1, 0); + smb2_util_close(tree, io.out.file.handle); + } + + done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname2); + smb2_util_rmdir(tree, dname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_upgrade(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h = {{0}}; + struct smb2_handle hnew = {{0}}; + NTSTATUS status; + const char *fname = "lease_upgrade.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + /* Grab a RH lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE1, 0); + h = io.out.file.handle; + + /* Upgrades (sidegrades?) to RW leave us with an RH. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE1, 0); + hnew = io.out.file.handle; + + smb2_util_close(tree, hnew); + + /* Upgrade to RHW lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1, 0); + hnew = io.out.file.handle; + + smb2_util_close(tree, h); + h = hnew; + + /* Attempt to downgrade - original lease state is maintained. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1, 0); + hnew = io.out.file.handle; + + smb2_util_close(tree, hnew); + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, hnew); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * upgrade2 test. + * full matrix of lease upgrade combinations + * (non-contended case) + * + * The summary of the behaviour is this: + * ------------------------------------- + * An uncontended lease upgrade results in a change + * if and only if the requested lease state is + * - valid, and + * - strictly a superset of the lease state already held. + * + * In that case the resulting lease state is the one + * requested in the upgrade. + */ +struct lease_upgrade2_test { + const char *initial; + const char *upgrade_to; + const char *expected; +}; + +#define NUM_LEASE_TYPES 5 +#define NUM_UPGRADE_TESTS ( NUM_LEASE_TYPES * NUM_LEASE_TYPES ) +struct lease_upgrade2_test lease_upgrade2_tests[NUM_UPGRADE_TESTS] = { + { "", "", "" }, + { "", "R", "R" }, + { "", "RH", "RH" }, + { "", "RW", "RW" }, + { "", "RWH", "RWH" }, + + { "R", "", "R" }, + { "R", "R", "R" }, + { "R", "RH", "RH" }, + { "R", "RW", "RW" }, + { "R", "RWH", "RWH" }, + + { "RH", "", "RH" }, + { "RH", "R", "RH" }, + { "RH", "RH", "RH" }, + { "RH", "RW", "RH" }, + { "RH", "RWH", "RWH" }, + + { "RW", "", "RW" }, + { "RW", "R", "RW" }, + { "RW", "RH", "RW" }, + { "RW", "RW", "RW" }, + { "RW", "RWH", "RWH" }, + + { "RWH", "", "RWH" }, + { "RWH", "R", "RWH" }, + { "RWH", "RH", "RWH" }, + { "RWH", "RW", "RWH" }, + { "RWH", "RWH", "RWH" }, +}; + +static bool test_lease_upgrade2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle h, hnew; + NTSTATUS status; + struct smb2_create io; + struct smb2_lease ls; + const char *fname = "lease_upgrade2.dat"; + bool ret = true; + int i; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + for (i = 0; i < NUM_UPGRADE_TESTS; i++) { + struct lease_upgrade2_test t = lease_upgrade2_tests[i]; + + smb2_util_unlink(tree, fname); + + /* Grab a lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state(t.initial)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, t.initial, true, LEASE1, 0); + h = io.out.file.handle; + + /* Upgrade. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state(t.upgrade_to)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, t.expected, true, LEASE1, 0); + hnew = io.out.file.handle; + + smb2_util_close(tree, hnew); + smb2_util_close(tree, h); + } + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, hnew); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + + +/** + * upgrade3: + * full matrix of lease upgrade combinations + * (contended case) + * + * We start with 2 leases, and check how one can + * be upgraded + * + * The summary of the behaviour is this: + * ------------------------------------- + * + * If we have two leases (lease1 and lease2) on the same file, + * then attempt to upgrade lease1 results in a change if and only + * if the requested lease state: + * - is valid, + * - is strictly a superset of lease1, and + * - can held together with lease2. + * + * In that case, the resuling lease state of the upgraded lease1 + * is the state requested in the upgrade. lease2 is not broken + * and remains unchanged. + * + * Note that this contrasts the case of directly opening with + * an initial requested lease state, in which case you get that + * portion of the requested state that can be shared with the + * already existing leases (or the states that they get broken to). + */ +struct lease_upgrade3_test { + const char *held1; + const char *held2; + const char *upgrade_to; + const char *upgraded_to; +}; + +#define NUM_UPGRADE3_TESTS ( 20 ) +struct lease_upgrade3_test lease_upgrade3_tests[NUM_UPGRADE3_TESTS] = { + {"R", "R", "", "R" }, + {"R", "R", "R", "R" }, + {"R", "R", "RW", "R" }, + {"R", "R", "RH", "RH" }, + {"R", "R", "RHW", "R" }, + + {"R", "RH", "", "R" }, + {"R", "RH", "R", "R" }, + {"R", "RH", "RW", "R" }, + {"R", "RH", "RH", "RH" }, + {"R", "RH", "RHW", "R" }, + + {"RH", "R", "", "RH" }, + {"RH", "R", "R", "RH" }, + {"RH", "R", "RW", "RH" }, + {"RH", "R", "RH", "RH" }, + {"RH", "R", "RHW", "RH" }, + + {"RH", "RH", "", "RH" }, + {"RH", "RH", "R", "RH" }, + {"RH", "RH", "RW", "RH" }, + {"RH", "RH", "RH", "RH" }, + {"RH", "RH", "RHW", "RH" }, +}; + +static bool test_lease_upgrade3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle h, h2, hnew; + NTSTATUS status; + struct smb2_create io; + struct smb2_lease ls; + const char *fname = "lease_upgrade3.dat"; + bool ret = true; + int i; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + smb2_util_unlink(tree, fname); + + for (i = 0; i < NUM_UPGRADE3_TESTS; i++) { + struct lease_upgrade3_test t = lease_upgrade3_tests[i]; + + smb2_util_unlink(tree, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* grab first lease */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state(t.held1)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, t.held1, true, LEASE1, 0); + h = io.out.file.handle; + + /* grab second lease */ + smb2_lease_create(&io, &ls, false, fname, LEASE2, smb2_util_lease_state(t.held2)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, t.held2, true, LEASE2, 0); + h2 = io.out.file.handle; + + /* no break has happened */ + CHECK_VAL(lease_break_info.count, 0); + CHECK_VAL(lease_break_info.failures, 0); + + /* try to upgrade lease1 */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state(t.upgrade_to)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, t.upgraded_to, true, LEASE1, 0); + hnew = io.out.file.handle; + + /* no break has happened */ + CHECK_VAL(lease_break_info.count, 0); + CHECK_VAL(lease_break_info.failures, 0); + + smb2_util_close(tree, hnew); + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + } + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, hnew); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + + + +/* + break_results should be read as "held lease, new lease, hold broken to, new + grant", i.e. { "RH", "RW", "RH", "R" } means that if key1 holds RH and key2 + tries for RW, key1 will be broken to RH (in this case, not broken at all) + and key2 will be granted R. + + Note: break_results only includes things that Win7 will actually grant (see + request_results above). + */ +#define NBREAK_RESULTS 16 +static const char *break_results[NBREAK_RESULTS][4] = { + {"R", "R", "R", "R"}, + {"R", "RH", "R", "RH"}, + {"R", "RW", "R", "R"}, + {"R", "RHW", "R", "RH"}, + + {"RH", "R", "RH", "R"}, + {"RH", "RH", "RH", "RH"}, + {"RH", "RW", "RH", "R"}, + {"RH", "RHW", "RH", "RH"}, + + {"RW", "R", "R", "R"}, + {"RW", "RH", "R", "RH"}, + {"RW", "RW", "R", "R"}, + {"RW", "RHW", "R", "RH"}, + + {"RHW", "R", "RH", "R"}, + {"RHW", "RH", "RH", "RH"}, + {"RHW", "RW", "RH", "R"}, + {"RHW", "RHW", "RH", "RH"}, +}; + +static bool test_lease_break(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h, h2, h3; + NTSTATUS status; + const char *fname = "lease_break.dat"; + bool ret = true; + int i; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + smb2_util_unlink(tree, fname); + + for (i = 0; i < NBREAK_RESULTS; i++) { + const char *held = break_results[i][0]; + const char *contend = break_results[i][1]; + const char *brokento = break_results[i][2]; + const char *granted = break_results[i][3]; + torture_comment(tctx, "Hold %s(%x), requesting %s(%x), " + "expecting break to %s(%x) and grant of %s(%x)\n", + held, smb2_util_lease_state(held), contend, smb2_util_lease_state(contend), + brokento, smb2_util_lease_state(brokento), granted, smb2_util_lease_state(granted)); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state(held)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, held, true, LEASE1, 0); + + /* Possibly contend lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE2, smb2_util_lease_state(contend)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, granted, true, LEASE2, 0); + + if (smb2_util_lease_state(held) != smb2_util_lease_state(brokento)) { + CHECK_BREAK_INFO(held, brokento, LEASE1); + } else { + CHECK_NO_BREAK(tctx); + } + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + Now verify that an attempt to upgrade LEASE1 results in no + break and no change in LEASE1. + */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, brokento, true, LEASE1, 0); + CHECK_VAL(lease_break_info.count, 0); + CHECK_VAL(lease_break_info.failures, 0); + + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + } + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_nobreakself(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + const char *fname = "lease_nobreakself.dat"; + bool ret = true; + uint32_t caps; + char c = 0; + + caps = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + /* Win7 is happy to grant RHW leases on files. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, + smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "R", true, LEASE1, 0); + + smb2_lease_create(&io, &ls, false, fname, LEASE2, + smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_LEASE(&io, "R", true, LEASE2, 0); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + /* Make sure we don't break ourselves on write */ + + status = smb2_util_write(tree, h1, &c, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_BREAK_INFO("R", "", LEASE2); + + /* Try the other way round. First, upgrade LEASE2 to R again */ + + smb2_lease_create(&io, &ls, false, fname, LEASE2, + smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE(&io, "R", true, LEASE2, 0); + smb2_util_close(tree, io.out.file.handle); + + /* Now break LEASE1 via h2 */ + + torture_reset_lease_break_info(tctx, &lease_break_info); + status = smb2_util_write(tree, h2, &c, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_BREAK_INFO("R", "", LEASE1); + + /* .. and break LEASE2 via h1 */ + + torture_reset_lease_break_info(tctx, &lease_break_info); + status = smb2_util_write(tree, h1, &c, 0, 1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_BREAK_INFO("R", "", LEASE2); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_statopen(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + const char *fname = "lease_statopen.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + /* Create file. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + smb2_util_close(tree, h1); + + /* Stat open file with RWH lease. */ + smb2_lease_create_share(&io, &ls, false, fname, 0, LEASE1, + smb2_util_lease_state("RWH")); + io.in.desired_access = FILE_READ_ATTRIBUTES; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + /* Ensure non-stat open doesn't break and gets same lease + state as existing stat open. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, + smb2_util_lease_state("")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + + CHECK_NO_BREAK(tctx); + smb2_util_close(tree, h1); + + /* Open with conflicting lease. stat open should break down to RH */ + smb2_lease_create(&io, &ls, false, fname, LEASE2, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE2, 0); + + CHECK_BREAK_INFO("RWH", "RH", LEASE1); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_statopen2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + NTSTATUS status; + const char *fname = "lease_statopen2.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + torture_reset_lease_break_info(tctx, &lease_break_info); + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + /* Open file with RWH lease. */ + smb2_lease_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = io.out.file.handle; + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + + /* Stat open */ + ZERO_STRUCT(io); + io.in.desired_access = FILE_READ_ATTRIBUTES; + io.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.fname = fname; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h2 = io.out.file.handle; + + /* Open file with RWH lease. */ + smb2_lease_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h3 = io.out.file.handle; + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + +done: + if (!smb2_util_handle_empty(h3)) { + smb2_util_close(tree, h3); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_statopen3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + const char *fname = "lease_statopen3.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + torture_reset_lease_break_info(tctx, &lease_break_info); + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + /* Stat open */ + ZERO_STRUCT(io); + io.in.desired_access = FILE_READ_ATTRIBUTES; + io.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.fname = fname; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = io.out.file.handle; + + /* Open file with RWH lease. */ + smb2_lease_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h2 = io.out.file.handle; + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_statopen4_do(struct torture_context *tctx, + struct smb2_tree *tree, + uint32_t access_mask, + bool expect_stat_open) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + NTSTATUS status; + const char *fname = "lease_statopen2.dat"; + bool ret = true; + + /* Open file with RWH lease. */ + smb2_lease_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + io.in.desired_access = SEC_FILE_ALL; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = io.out.file.handle; + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + + /* Stat open */ + ZERO_STRUCT(io); + io.in.desired_access = access_mask; + io.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.fname = fname; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h2 = io.out.file.handle; + + if (expect_stat_open) { + CHECK_NO_BREAK(tctx); + if (!ret) { + goto done; + } + } else { + CHECK_VAL(lease_break_info.count, 1); + if (!ret) { + goto done; + } + /* + * Don't bother checking the lease state of an additional open + * below... + */ + goto done; + } + + /* Open file with RWH lease. */ + smb2_lease_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + io.in.desired_access = SEC_FILE_WRITE_DATA; + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h3 = io.out.file.handle; + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + +done: + if (!smb2_util_handle_empty(h3)) { + smb2_util_close(tree, h3); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_statopen4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "lease_statopen4.dat"; + struct smb2_handle h1 = {{0}}; + uint32_t caps; + size_t i; + NTSTATUS status; + bool ret = true; + struct { + uint32_t access_mask; + bool expect_stat_open; + } tests[] = { + { + .access_mask = FILE_READ_DATA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_WRITE_DATA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_READ_EA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_WRITE_EA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_EXECUTE, + .expect_stat_open = false, + }, + { + .access_mask = FILE_READ_ATTRIBUTES, + .expect_stat_open = true, + }, + { + .access_mask = FILE_WRITE_ATTRIBUTES, + .expect_stat_open = true, + }, + { + .access_mask = DELETE_ACCESS, + .expect_stat_open = false, + }, + { + .access_mask = READ_CONTROL_ACCESS, + .expect_stat_open = true, + }, + { + .access_mask = WRITE_DAC_ACCESS, + .expect_stat_open = false, + }, + { + .access_mask = WRITE_OWNER_ACCESS, + .expect_stat_open = false, + }, + { + .access_mask = SYNCHRONIZE_ACCESS, + .expect_stat_open = true, + }, + }; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + torture_reset_lease_break_info(tctx, &lease_break_info); + + ret = test_lease_statopen4_do(tctx, + tree, + tests[i].access_mask, + tests[i].expect_stat_open); + if (ret == true) { + continue; + } + torture_result(tctx, TORTURE_FAIL, + "test %zu: access_mask: %s, " + "expect_stat_open: %s\n", + i, + get_sec_mask_str(tree, tests[i].access_mask), + tests[i].expect_stat_open ? "yes" : "no"); + goto done; + } + +done: + smb2_util_unlink(tree, fname); + return ret; +} + +static void torture_oplock_break_callback(struct smb2_request *req) +{ + NTSTATUS status; + struct smb2_break br; + + ZERO_STRUCT(br); + status = smb2_break_recv(req, &br); + if (!NT_STATUS_IS_OK(status)) + lease_break_info.oplock_failures++; + + return; +} + +/* a oplock break request handler */ +static bool torture_oplock_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, void *private_data) +{ + struct smb2_tree *tree = private_data; + struct smb2_request *req; + struct smb2_break br; + + lease_break_info.oplock_handle = *handle; + lease_break_info.oplock_level = level; + lease_break_info.oplock_count++; + + ZERO_STRUCT(br); + br.in.file.handle = *handle; + br.in.oplock_level = level; + + if (lease_break_info.held_oplock_level > SMB2_OPLOCK_LEVEL_II) { + req = smb2_break_send(tree, &br); + req->async.fn = torture_oplock_break_callback; + req->async.private_data = NULL; + } + lease_break_info.held_oplock_level = level; + + return true; +} + +#define NOPLOCK_RESULTS 12 +static const char *oplock_results[NOPLOCK_RESULTS][4] = { + {"R", "s", "R", "s"}, + {"R", "x", "R", "s"}, + {"R", "b", "R", "s"}, + + {"RH", "s", "RH", ""}, + {"RH", "x", "RH", ""}, + {"RH", "b", "RH", ""}, + + {"RW", "s", "R", "s"}, + {"RW", "x", "R", "s"}, + {"RW", "b", "R", "s"}, + + {"RHW", "s", "RH", ""}, + {"RHW", "x", "RH", ""}, + {"RHW", "b", "RH", ""}, +}; + +static const char *oplock_results_2[NOPLOCK_RESULTS][4] = { + {"s", "R", "s", "R"}, + {"s", "RH", "s", "R"}, + {"s", "RW", "s", "R"}, + {"s", "RHW", "s", "R"}, + + {"x", "R", "s", "R"}, + {"x", "RH", "s", "R"}, + {"x", "RW", "s", "R"}, + {"x", "RHW", "s", "R"}, + + {"b", "R", "s", "R"}, + {"b", "RH", "s", "R"}, + {"b", "RW", "s", "R"}, + {"b", "RHW", "s", "R"}, +}; + +static bool test_lease_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h, h2; + NTSTATUS status; + const char *fname = "lease_oplock.dat"; + bool ret = true; + int i; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + smb2_util_unlink(tree, fname); + + for (i = 0; i < NOPLOCK_RESULTS; i++) { + const char *held = oplock_results[i][0]; + const char *contend = oplock_results[i][1]; + const char *brokento = oplock_results[i][2]; + const char *granted = oplock_results[i][3]; + torture_comment(tctx, "Hold %s(%x), requesting %s(%x), " + "expecting break to %s(%x) and grant of %s(%x)\n", + held, smb2_util_lease_state(held), contend, smb2_util_oplock_level(contend), + brokento, smb2_util_lease_state(brokento), granted, smb2_util_oplock_level(granted)); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state(held)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, held, true, LEASE1, 0); + + /* Does an oplock contend the lease? */ + smb2_oplock_create(&io, fname, smb2_util_oplock_level(contend)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(granted)); + lease_break_info.held_oplock_level = io.out.oplock_level; + + if (smb2_util_lease_state(held) != smb2_util_lease_state(brokento)) { + CHECK_BREAK_INFO(held, brokento, LEASE1); + } else { + CHECK_NO_BREAK(tctx); + } + + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + } + + for (i = 0; i < NOPLOCK_RESULTS; i++) { + const char *held = oplock_results_2[i][0]; + const char *contend = oplock_results_2[i][1]; + const char *brokento = oplock_results_2[i][2]; + const char *granted = oplock_results_2[i][3]; + torture_comment(tctx, "Hold %s(%x), requesting %s(%x), " + "expecting break to %s(%x) and grant of %s(%x)\n", + held, smb2_util_oplock_level(held), contend, smb2_util_lease_state(contend), + brokento, smb2_util_oplock_level(brokento), granted, smb2_util_lease_state(granted)); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab an oplock. */ + smb2_oplock_create(&io, fname, smb2_util_oplock_level(held)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(held)); + lease_break_info.held_oplock_level = io.out.oplock_level; + + /* Grab lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state(contend)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, granted, true, LEASE1, 0); + + if (smb2_util_oplock_level(held) != smb2_util_oplock_level(brokento)) { + CHECK_OPLOCK_BREAK(brokento); + } else { + CHECK_NO_BREAK(tctx); + } + + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + } + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_multibreak(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + struct smb2_write w; + NTSTATUS status; + const char *fname = "lease_multibreak.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + smb2_util_unlink(tree, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab lease, upgrade to RHW .. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE1, 0); + + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1, 0); + + /* Contend with LEASE2. */ + smb2_lease_create(&io, &ls, false, fname, LEASE2, smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE2, 0); + + /* Verify that we were only sent one break. */ + CHECK_BREAK_INFO("RHW", "RH", LEASE1); + + /* Drop LEASE1 / LEASE2 */ + status = smb2_util_close(tree, h); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_close(tree, h2); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_close(tree, h3); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab an R lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "R", true, LEASE1, 0); + + /* Grab a level-II oplock. */ + smb2_oplock_create(&io, fname, smb2_util_oplock_level("s")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + lease_break_info.held_oplock_level = io.out.oplock_level; + + /* Verify no breaks. */ + CHECK_NO_BREAK(tctx); + + /* Open for truncate, force a break. */ + smb2_generic_create(&io, NULL, false, fname, + NTCREATEX_DISP_OVERWRITE_IF, smb2_util_oplock_level(""), 0, 0); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + CHECK_CREATED(&io, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("")); + lease_break_info.held_oplock_level = io.out.oplock_level; + + /* Sleep, use a write to clear the recv queue. */ + smb_msleep(250); + ZERO_STRUCT(w); + w.in.file.handle = h3; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Verify one oplock break, one lease break. */ + CHECK_OPLOCK_BREAK(""); + CHECK_BREAK_INFO("R", "", LEASE1); + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_v2_request_parent(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + uint64_t parent = LEASE2; + NTSTATUS status; + const char *fname = "lease_v2_request_parent.dat"; + bool ret = true; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + if (!(caps & SMB2_CAP_DIRECTORY_LEASING)) { + torture_skip(tctx, "directory leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, &parent, + smb2_util_lease_state("RHW"), + 0x11); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, + SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET, LEASE2, + ls.lease_epoch + 1); + + done: + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_break_twice(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h1 = {{0}}; + NTSTATUS status; + const char *fname = "lease_break_twice.dat"; + bool ret = true; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + ZERO_STRUCT(io); + + smb2_lease_v2_create_share( + &io, &ls1, false, fname, smb2_util_share_access("RWD"), + LEASE1, NULL, smb2_util_lease_state("RWH"), 0x11); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1.lease_epoch + 1); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_lease_v2_create_share( + &io, &ls2, false, fname, smb2_util_share_access("R"), + LEASE2, NULL, smb2_util_lease_state("RWH"), 0x22); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + CHECK_BREAK_INFO_V2(tree->session->transport, + "RWH", "RW", LEASE1, ls1.lease_epoch + 2); + + smb2_lease_v2_create_share( + &io, &ls2, false, fname, smb2_util_share_access("RWD"), + LEASE2, NULL, smb2_util_lease_state("RWH"), 0x22); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_V2(&io, "RH", true, LEASE2, 0, 0, ls2.lease_epoch + 1); + CHECK_BREAK_INFO_V2(tree->session->transport, + "RW", "R", LEASE1, ls1.lease_epoch + 3); + +done: + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_v2_request(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1, ls2, ls2t, ls3, ls4; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + struct smb2_handle h4 = {{0}}; + struct smb2_handle h5 = {{0}}; + struct smb2_write w; + NTSTATUS status; + const char *fname = "lease_v2_request.dat"; + const char *dname = "lease_v2_request.dir"; + const char *dnamefname = "lease_v2_request.dir\\lease.dat"; + const char *dnamefname2 = "lease_v2_request.dir\\lease2.dat"; + bool ret = true; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + if (!(caps & SMB2_CAP_DIRECTORY_LEASING)) { + torture_skip(tctx, "directory leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, dname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RHW"), + 0x11); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1.lease_epoch + 1); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls2, true, dname, + smb2_util_share_access("RWD"), + LEASE2, NULL, + smb2_util_lease_state("RHW"), + 0x22); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_DIRECTORY); + CHECK_LEASE_V2(&io, "RH", true, LEASE2, 0, 0, ls2.lease_epoch + 1); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls3, false, dnamefname, + smb2_util_share_access("RWD"), + LEASE3, &LEASE2, + smb2_util_lease_state("RHW"), + 0x33); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE3, + SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET, LEASE2, + ls3.lease_epoch + 1); + + CHECK_NO_BREAK(tctx); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls4, false, dnamefname2, + smb2_util_share_access("RWD"), + LEASE4, NULL, + smb2_util_lease_state("RHW"), + 0x44); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h4 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE4, 0, 0, ls4.lease_epoch + 1); + + CHECK_BREAK_INFO_V2(tree->session->transport, + "RH", "", LEASE2, ls2.lease_epoch + 2); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls2t, true, dname, + smb2_util_share_access("RWD"), + LEASE2, NULL, + smb2_util_lease_state("RHW"), + 0x222); + io.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h5 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_DIRECTORY); + CHECK_LEASE_V2(&io, "RH", true, LEASE2, 0, 0, ls2.lease_epoch+3); + smb2_util_close(tree, h5); + + ZERO_STRUCT(w); + w.in.file.handle = h4; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Wait 4 seconds in order to check if the write time + * was updated (after 2 seconds). + */ + smb_msleep(4000); + CHECK_NO_BREAK(tctx); + + /* + * only the close on the modified file break the + * directory lease. + */ + smb2_util_close(tree, h4); + + CHECK_BREAK_INFO_V2(tree->session->transport, + "RH", "", LEASE2, ls2.lease_epoch+4); + + done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + smb2_util_close(tree, h4); + smb2_util_close(tree, h5); + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, dname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_v2_epoch1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h; + const char *fname = "lease_v2_epoch1.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RHW"), + 0x4711); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls.lease_epoch + 1); + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + + smb2_lease_v2_create_share(&io, &ls, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RHW"), + 0x11); + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RWH", true, LEASE1, 0, 0, ls.lease_epoch + 1); + smb2_util_close(tree, h); + +done: + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_v2_epoch2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1v2, ls1v2t, ls1v1; + struct smb2_handle hv2 = {}, hv1 = {}; + const char *fname = "lease_v2_epoch2.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls1v2, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("R"), + 0x4711); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv2 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "R", true, LEASE1, 0, 0, ls1v2.lease_epoch + 1); + + ZERO_STRUCT(io); + smb2_lease_create_share(&io, &ls1v1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RH", true, LEASE1, 0, 0, ls1v2.lease_epoch + 2); + + smb2_util_close(tree, hv2); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls1v2t, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RHW"), + 0x11); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1v2.lease_epoch + 3); + + smb2_util_close(tree, hv2); + + smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_NONE); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_BREAK_INFO_V2(tree->session->transport, + "RWH", "RH", LEASE1, ls1v2.lease_epoch + 4); + + smb2_util_close(tree, hv2); + smb2_util_close(tree, hv1); + + ZERO_STRUCT(io); + smb2_lease_create_share(&io, &ls1v1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1, 0); + + smb2_util_close(tree, hv1); + +done: + smb2_util_close(tree, hv2); + smb2_util_close(tree, hv1); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_v2_epoch3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1v1 = {}, ls1v1t = {},ls1v2 = {}; + struct smb2_handle hv1 = {}, hv2 = {}; + const char *fname = "lease_v2_epoch3.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(io); + smb2_lease_create_share(&io, &ls1v1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "R", true, LEASE1, 0); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls1v2, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RW"), + 0x4711); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RW", true, LEASE1, 0); + + smb2_util_close(tree, hv1); + + ZERO_STRUCT(io); + smb2_lease_create_share(&io, &ls1v1t, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + + smb2_util_close(tree, hv1); + + smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_NONE); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_BREAK_INFO("RWH", "RH", LEASE1); + + smb2_util_close(tree, hv1); + smb2_util_close(tree, hv2); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls1v2, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RWH"), + 0x4711); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + hv2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1v2.lease_epoch + 1); + smb2_util_close(tree, hv2); + +done: + smb2_util_close(tree, hv2); + smb2_util_close(tree, hv1); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_breaking1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_lease ls1 = {}; + struct smb2_handle h1a = {}; + struct smb2_handle h1b = {}; + struct smb2_handle h2 = {}; + struct smb2_request *req2 = NULL; + struct smb2_lease_break_ack ack = {}; + const char *fname = "lease_breaking1.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, 0); + + /* + * a conflicting open is blocked until we ack the + * lease break + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + /* + * we got the lease break, but defer the ack. + */ + CHECK_BREAK_INFO("RWH", "RH", LEASE1); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS); + smb2_util_close(tree, h1b); + + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + /* + * We ack the lease break. + */ + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "RH", LEASE1); + + torture_assert(tctx, req2->cancel.can_cancel, + "req2 can_cancel"); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_NO_BREAK(tctx); +done: + smb2_util_close(tree, h1a); + smb2_util_close(tree, h1b); + smb2_util_close(tree, h2); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_breaking2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_lease ls1 = {}; + struct smb2_handle h1a = {}; + struct smb2_handle h1b = {}; + struct smb2_handle h2 = {}; + struct smb2_request *req2 = NULL; + struct smb2_lease_break_ack ack = {}; + const char *fname = "lease_breaking2.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, 0); + + /* + * a conflicting open is blocked until we ack the + * lease break + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + io2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + /* + * we got the lease break, but defer the ack. + */ + CHECK_BREAK_INFO("RWH", "", LEASE1); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS); + smb2_util_close(tree, h1b); + + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + /* + * We ack the lease break. + */ + ack.in.lease.lease_state = + SMB2_LEASE_READ | SMB2_LEASE_WRITE | SMB2_LEASE_HANDLE; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_REQUEST_NOT_ACCEPTED); + + ack.in.lease.lease_state = + SMB2_LEASE_READ | SMB2_LEASE_WRITE; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_REQUEST_NOT_ACCEPTED); + + ack.in.lease.lease_state = + SMB2_LEASE_WRITE | SMB2_LEASE_HANDLE; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_REQUEST_NOT_ACCEPTED); + + ack.in.lease.lease_state = + SMB2_LEASE_READ | SMB2_LEASE_HANDLE; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_REQUEST_NOT_ACCEPTED); + + ack.in.lease.lease_state = SMB2_LEASE_WRITE; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_REQUEST_NOT_ACCEPTED); + + ack.in.lease.lease_state = SMB2_LEASE_HANDLE; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_REQUEST_NOT_ACCEPTED); + + ack.in.lease.lease_state = SMB2_LEASE_READ; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_REQUEST_NOT_ACCEPTED); + + /* Try again with the correct state this time. */ + ack.in.lease.lease_state = SMB2_LEASE_NONE;; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "", LEASE1); + + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_UNSUCCESSFUL); + + torture_assert(tctx, req2->cancel.can_cancel, + "req2 can_cancel"); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_NO_BREAK(tctx); + + /* Get state of the original handle. */ + smb2_lease_create(&io1, &ls1, false, fname, LEASE1, smb2_util_lease_state("")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE(&io1, "", true, LEASE1, 0); + smb2_util_close(tree, io1.out.file.handle); + +done: + smb2_util_close(tree, h1a); + smb2_util_close(tree, h1b); + smb2_util_close(tree, h2); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_breaking3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_create io3 = {}; + struct smb2_lease ls1 = {}; + struct smb2_handle h1a = {}; + struct smb2_handle h1b = {}; + struct smb2_handle h2 = {}; + struct smb2_handle h3 = {}; + struct smb2_request *req2 = NULL; + struct smb2_request *req3 = NULL; + struct lease_break_info lease_break_info_tmp = {}; + struct smb2_lease_break_ack ack = {}; + const char *fname = "lease_breaking3.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, 0); + + /* + * a conflicting open is blocked until we ack the + * lease break + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + /* + * we got the lease break, but defer the ack. + */ + CHECK_BREAK_INFO("RWH", "RH", LEASE1); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS); + smb2_util_close(tree, h1b); + + /* + * a conflicting open with NTCREATEX_DISP_OVERWRITE + * doesn't trigger an immediate lease break to none. + */ + lease_break_info_tmp = lease_break_info; + torture_reset_lease_break_info(tctx, &lease_break_info); + smb2_oplock_create(&io3, fname, SMB2_OPLOCK_LEVEL_NONE); + io3.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + req3 = smb2_create_send(tree, &io3); + torture_assert(tctx, req3 != NULL, "smb2_create_send"); + CHECK_NO_BREAK(tctx); + lease_break_info = lease_break_info_tmp; + + torture_assert(tctx, req3->state == SMB2_REQUEST_RECV, "req3 pending"); + + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS); + smb2_util_close(tree, h1b); + + CHECK_NO_BREAK(tctx); + + /* + * We ack the lease break, but defer acking the next break (to "R") + */ + lease_break_info.lease_skip_ack = true; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "RH", LEASE1); + + /* + * We got an additional break downgrading to just "R" + * while we defer the ack. + */ + CHECK_BREAK_INFO("RH", "R", LEASE1); + + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS); + smb2_util_close(tree, h1b); + + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + torture_assert(tctx, req3->state == SMB2_REQUEST_RECV, "req3 pending"); + + /* + * We ack the downgrade to "R" and get an immediate break to none + */ + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "R", LEASE1); + + /* + * We get the downgrade to none. + */ + CHECK_BREAK_INFO("R", "", LEASE1); + + torture_assert(tctx, req2->cancel.can_cancel, + "req2 can_cancel"); + torture_assert(tctx, req3->cancel.can_cancel, + "req3 can_cancel"); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + status = smb2_create_recv(req3, tctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io3.out.file.handle; + CHECK_CREATED(&io3, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io3.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_NO_BREAK(tctx); +done: + smb2_util_close(tree, h1a); + smb2_util_close(tree, h1b); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_v2_breaking3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_create io3 = {}; + struct smb2_lease ls1 = {}; + struct smb2_handle h1a = {}; + struct smb2_handle h1b = {}; + struct smb2_handle h2 = {}; + struct smb2_handle h3 = {}; + struct smb2_request *req2 = NULL; + struct smb2_request *req3 = NULL; + struct lease_break_info lease_break_info_tmp = {}; + struct smb2_lease_break_ack ack = {}; + const char *fname = "v2_lease_breaking3.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_lease_v2_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RHW"), + 0x11); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + /* Epoch increases on open. */ + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io1, "RHW", true, LEASE1, 0, 0, ls1.lease_epoch); + + /* + * a conflicting open is blocked until we ack the + * lease break + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + /* + * we got the lease break, but defer the ack. + */ + CHECK_BREAK_INFO_V2(tree->session->transport, + "RWH", "RH", LEASE1, ls1.lease_epoch + 1); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + /* On receiving a lease break, we must sync the new epoch. */ + ls1.lease_epoch = lease_break_info.lease_break.new_epoch; + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io1, "RHW", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS, 0, ls1.lease_epoch); + smb2_util_close(tree, h1b); + + /* + * a conflicting open with NTCREATEX_DISP_OVERWRITE + * doesn't trigger an immediate lease break to none. + */ + lease_break_info_tmp = lease_break_info; + torture_reset_lease_break_info(tctx, &lease_break_info); + smb2_oplock_create(&io3, fname, SMB2_OPLOCK_LEVEL_NONE); + io3.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + req3 = smb2_create_send(tree, &io3); + torture_assert(tctx, req3 != NULL, "smb2_create_send"); + CHECK_NO_BREAK(tctx); + lease_break_info = lease_break_info_tmp; + + torture_assert(tctx, req3->state == SMB2_REQUEST_RECV, "req3 pending"); + + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io1, "RHW", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS, 0, ls1.lease_epoch); + smb2_util_close(tree, h1b); + + CHECK_NO_BREAK(tctx); + + /* + * We ack the lease break, but defer acking the next break (to "R") + */ + lease_break_info.lease_skip_ack = true; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "RH", LEASE1); + + /* + * We got an additional break downgrading to just "R" + * while we defer the ack. + */ + CHECK_BREAK_INFO_V2(tree->session->transport, + "RH", "R", LEASE1, ls1.lease_epoch); + /* On receiving a lease break, we must sync the new epoch. */ + ls1.lease_epoch = lease_break_info.lease_break.new_epoch; + + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io1, "RH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS, 0, ls1.lease_epoch); + smb2_util_close(tree, h1b); + + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + torture_assert(tctx, req3->state == SMB2_REQUEST_RECV, "req3 pending"); + + /* + * We ack the downgrade to "R" and get an immediate break to none + */ + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "R", LEASE1); + + /* + * We get the downgrade to none. + */ + CHECK_BREAK_INFO_V2(tree->session->transport, + "R", "", LEASE1, ls1.lease_epoch); + + torture_assert(tctx, req2->cancel.can_cancel, + "req2 can_cancel"); + torture_assert(tctx, req3->cancel.can_cancel, + "req3 can_cancel"); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + status = smb2_create_recv(req3, tctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io3.out.file.handle; + CHECK_CREATED(&io3, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io3.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_NO_BREAK(tctx); +done: + smb2_util_close(tree, h1a); + smb2_util_close(tree, h1b); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + + +static bool test_lease_breaking4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_create io3 = {}; + struct smb2_lease ls1 = {}; + struct smb2_lease ls1t = {}; + struct smb2_handle h1 = {}; + struct smb2_handle h2 = {}; + struct smb2_handle h3 = {}; + struct smb2_request *req2 = NULL; + struct lease_break_info lease_break_info_tmp = {}; + struct smb2_lease_break_ack ack = {}; + const char *fname = "lease_breaking4.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RH")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RH", true, LEASE1, 0); + + CHECK_NO_BREAK(tctx); + + /* + * a conflicting open is *not* blocked until we ack the + * lease break + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + io2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + /* + * We got a break from RH to NONE, we're supported to ack + * this downgrade + */ + CHECK_BREAK_INFO("RH", "", LEASE1); + + lease_break_info_tmp = lease_break_info; + torture_reset_lease_break_info(tctx, &lease_break_info); + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_DONE, "req2 done"); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + smb2_util_close(tree, h2); + + CHECK_NO_BREAK(tctx); + + /* + * a conflicting open is *not* blocked until we ack the + * lease break, even if the lease is in breaking state. + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + io2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_DONE, "req2 done"); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + smb2_util_close(tree, h2); + + CHECK_NO_BREAK(tctx); + + /* + * We now ask the server about the current lease state + * which should still be "RH", but with + * SMB2_LEASE_FLAG_BREAK_IN_PROGRESS. + */ + smb2_lease_create_share(&io3, &ls1t, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("")); + status = smb2_create(tree, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io3.out.file.handle; + CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io3, "RH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS); + + /* + * We finally ack the lease break... + */ + CHECK_NO_BREAK(tctx); + lease_break_info = lease_break_info_tmp; + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "", LEASE1); + + CHECK_NO_BREAK(tctx); + +done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_breaking5(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_create io3 = {}; + struct smb2_lease ls1 = {}; + struct smb2_lease ls1t = {}; + struct smb2_handle h1 = {}; + struct smb2_handle h2 = {}; + struct smb2_handle h3 = {}; + struct smb2_request *req2 = NULL; + struct lease_break_info lease_break_info_tmp = {}; + struct smb2_lease_break_ack ack = {}; + const char *fname = "lease_breaking5.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "R", true, LEASE1, 0); + + CHECK_NO_BREAK(tctx); + + /* + * a conflicting open is *not* blocked until we ack the + * lease break + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + io2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + /* + * We got a break from RH to NONE, we're supported to ack + * this downgrade + */ + CHECK_BREAK_INFO("R", "", LEASE1); + + lease_break_info_tmp = lease_break_info; + torture_reset_lease_break_info(tctx, &lease_break_info); + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_DONE, "req2 done"); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_NO_BREAK(tctx); + + /* + * We now ask the server about the current lease state + * which should still be "RH", but with + * SMB2_LEASE_FLAG_BREAK_IN_PROGRESS. + */ + smb2_lease_create_share(&io3, &ls1t, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("")); + status = smb2_create(tree, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io3.out.file.handle; + CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io3, "", true, LEASE1, 0); + + /* + * We send an ack without without being asked. + */ + CHECK_NO_BREAK(tctx); + lease_break_info = lease_break_info_tmp; + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_UNSUCCESSFUL); + + CHECK_NO_BREAK(tctx); + +done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_breaking6(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_lease ls1 = {}; + struct smb2_handle h1a = {}; + struct smb2_handle h1b = {}; + struct smb2_handle h2 = {}; + struct smb2_request *req2 = NULL; + struct smb2_lease_break_ack ack = {}; + const char *fname = "lease_breaking6.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, 0); + + /* + * a conflicting open is blocked until we ack the + * lease break + */ + smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE); + req2 = smb2_create_send(tree, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + /* + * we got the lease break, but defer the ack. + */ + CHECK_BREAK_INFO("RWH", "RH", LEASE1); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * a open using the same lease key is still works, + * but reports SMB2_LEASE_FLAG_BREAK_IN_PROGRESS + */ + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, SMB2_LEASE_FLAG_BREAK_IN_PROGRESS); + smb2_util_close(tree, h1b); + + CHECK_NO_BREAK(tctx); + + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + /* + * We are asked to break to "RH", but we are allowed to + * break to any of "RH", "R" or NONE. + */ + ack.in.lease.lease_state = SMB2_LEASE_NONE; + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "", LEASE1); + + torture_assert(tctx, req2->cancel.can_cancel, + "req2 can_cancel"); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + CHECK_NO_BREAK(tctx); +done: + smb2_util_close(tree, h1a); + smb2_util_close(tree, h1b); + smb2_util_close(tree, h2); + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_lock1(struct torture_context *tctx, + struct smb2_tree *tree1a, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1 = {}; + struct smb2_create io2 = {}; + struct smb2_create io3 = {}; + struct smb2_lease ls1 = {}; + struct smb2_lease ls2 = {}; + struct smb2_lease ls3 = {}; + struct smb2_handle h1 = {}; + struct smb2_handle h2 = {}; + struct smb2_handle h3 = {}; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + const char *fname = "locktest.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + struct smbcli_options options1; + struct smb2_tree *tree1b = NULL; + + options1 = tree1a->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree1a->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* Set up handlers. */ + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + tree1a->session->transport->lease.handler = torture_lease_handler; + tree1a->session->transport->lease.private_data = tree1a; + tree1a->session->transport->oplock.handler = torture_oplock_handler; + tree1a->session->transport->oplock.private_data = tree1a; + + /* create a new connection (same client_guid) */ + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1b)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + tree1b->session->transport->lease.handler = torture_lease_handler; + tree1b->session->transport->lease.private_data = tree1b; + tree1b->session->transport->oplock.handler = torture_oplock_handler; + tree1b->session->transport->oplock.private_data = tree1b; + + smb2_util_unlink(tree1a, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + ZERO_STRUCT(lck); + + /* Open a handle on tree1a. */ + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree1a, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RWH", true, LEASE1, 0); + + /* Open a second handle on tree1b. */ + smb2_lease_create_share(&io2, &ls2, false, fname, + smb2_util_share_access("RWD"), + LEASE2, + smb2_util_lease_state("RWH")); + status = smb2_create(tree1b, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "RH", true, LEASE2, 0); + /* And LEASE1 got broken to RH. */ + CHECK_BREAK_INFO("RWH", "RH", LEASE1); + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Now open a lease on a different client guid. */ + smb2_lease_create_share(&io3, &ls3, false, fname, + smb2_util_share_access("RWD"), + LEASE3, + smb2_util_lease_state("RWH")); + status = smb2_create(tree2, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io3.out.file.handle; + CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io3, "RH", true, LEASE3, 0); + /* Doesn't break. */ + CHECK_NO_BREAK(tctx); + + lck.in.locks = el; + /* + * Try and get get an exclusive byte + * range lock on H1 (LEASE1). + */ + + lck.in.lock_count = 1; + lck.in.lock_sequence = 1; + lck.in.file.handle = h1; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree1a, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* LEASE2 and LEASE3 should get broken to NONE. */ + torture_wait_for_lease_break(tctx); + torture_wait_for_lease_break(tctx); + torture_wait_for_lease_break(tctx); + torture_wait_for_lease_break(tctx); + + CHECK_VAL(lease_break_info.failures, 0); \ + CHECK_VAL(lease_break_info.count, 2); \ + + /* Get state of the H1 (LEASE1) */ + smb2_lease_create(&io1, &ls1, false, fname, LEASE1, smb2_util_lease_state("")); + status = smb2_create(tree1a, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + /* Should still be RH. */ + CHECK_LEASE(&io1, "RH", true, LEASE1, 0); + smb2_util_close(tree1a, io1.out.file.handle); + + /* Get state of the H2 (LEASE2) */ + smb2_lease_create(&io2, &ls2, false, fname, LEASE2, smb2_util_lease_state("")); + status = smb2_create(tree1b, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE(&io2, "", true, LEASE2, 0); + smb2_util_close(tree1b, io2.out.file.handle); + + /* Get state of the H3 (LEASE3) */ + smb2_lease_create(&io3, &ls3, false, fname, LEASE3, smb2_util_lease_state("")); + status = smb2_create(tree2, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE(&io3, "", true, LEASE3, 0); + smb2_util_close(tree2, io3.out.file.handle); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * Try and get get an exclusive byte + * range lock on H3 (LEASE3). + */ + lck.in.lock_count = 1; + lck.in.lock_sequence = 2; + lck.in.file.handle = h3; + el[0].offset = 100; + el[0].length = 1; + el[0].reserved = 0; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree2, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + /* LEASE1 got broken to NONE. */ + CHECK_BREAK_INFO("RH", "", LEASE1); + torture_reset_lease_break_info(tctx, &lease_break_info); + +done: + smb2_util_close(tree1a, h1); + smb2_util_close(tree1b, h2); + smb2_util_close(tree2, h3); + + smb2_util_unlink(tree1a, fname); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_complex1(struct torture_context *tctx, + struct smb2_tree *tree1a) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + struct smb2_write w; + NTSTATUS status; + const char *fname = "lease_complex1.dat"; + bool ret = true; + uint32_t caps; + struct smb2_tree *tree1b = NULL; + struct smbcli_options options1; + + options1 = tree1a->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree1a->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tree1a->session->transport->lease.handler = torture_lease_handler; + tree1a->session->transport->lease.private_data = tree1a; + tree1a->session->transport->oplock.handler = torture_oplock_handler; + tree1a->session->transport->oplock.private_data = tree1a; + + /* create a new connection (same client_guid) */ + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1b)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + tree1b->session->transport->lease.handler = torture_lease_handler; + tree1b->session->transport->lease.private_data = tree1b; + tree1b->session->transport->oplock.handler = torture_oplock_handler; + tree1b->session->transport->oplock.private_data = tree1b; + + smb2_util_unlink(tree1a, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab R lease over connection 1a */ + smb2_lease_create(&io1, &ls1, false, fname, LEASE1, smb2_util_lease_state("R")); + status = smb2_create(tree1a, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "R", true, LEASE1, 0); + + /* Upgrade to RWH over connection 1b */ + ls1.lease_state = smb2_util_lease_state("RWH"); + status = smb2_create(tree1b, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RHW", true, LEASE1, 0); + + /* close over connection 1b */ + status = smb2_util_close(tree1b, h2); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Contend with LEASE2. */ + smb2_lease_create(&io2, &ls2, false, fname, LEASE2, smb2_util_lease_state("R")); + status = smb2_create(tree1b, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "R", true, LEASE2, 0); + + /* Verify that we were only sent one break. */ + CHECK_BREAK_INFO("RHW", "RH", LEASE1); + + /* again RH over connection 1b doesn't change the epoch */ + ls1.lease_state = smb2_util_lease_state("RH"); + status = smb2_create(tree1b, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RH", true, LEASE1, 0); + + /* close over connection 1b */ + status = smb2_util_close(tree1b, h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(w); + w.in.file.handle = h; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree1a, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls2.lease_epoch += 1; + CHECK_BREAK_INFO("R", "", LEASE2); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(w); + w.in.file.handle = h3; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree1b, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls1.lease_epoch += 1; + CHECK_BREAK_INFO("RH", "", LEASE1); + + done: + smb2_util_close(tree1a, h); + smb2_util_close(tree1b, h2); + smb2_util_close(tree1b, h3); + + smb2_util_unlink(tree1a, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_v2_complex1(struct torture_context *tctx, + struct smb2_tree *tree1a) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + struct smb2_write w; + NTSTATUS status; + const char *fname = "lease_v2_complex1.dat"; + bool ret = true; + uint32_t caps; + enum protocol_types protocol; + struct smb2_tree *tree1b = NULL; + struct smbcli_options options1; + + options1 = tree1a->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree1a->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree1a->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + tree1a->session->transport->lease.handler = torture_lease_handler; + tree1a->session->transport->lease.private_data = tree1a; + tree1a->session->transport->oplock.handler = torture_oplock_handler; + tree1a->session->transport->oplock.private_data = tree1a; + + /* create a new connection (same client_guid) */ + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1b)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + tree1b->session->transport->lease.handler = torture_lease_handler; + tree1b->session->transport->lease.private_data = tree1b; + tree1b->session->transport->oplock.handler = torture_oplock_handler; + tree1b->session->transport->oplock.private_data = tree1b; + + smb2_util_unlink(tree1a, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab R lease over connection 1a */ + smb2_lease_v2_create(&io1, &ls1, false, fname, LEASE1, NULL, + smb2_util_lease_state("R"), 0x4711); + status = smb2_create(tree1a, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io1, "R", true, LEASE1, + 0, 0, ls1.lease_epoch); + + /* Upgrade to RWH over connection 1b */ + ls1.lease_state = smb2_util_lease_state("RWH"); + status = smb2_create(tree1b, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io1, "RHW", true, LEASE1, + 0, 0, ls1.lease_epoch); + + /* close over connection 1b */ + status = smb2_util_close(tree1b, h2); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Contend with LEASE2. */ + smb2_lease_v2_create(&io2, &ls2, false, fname, LEASE2, NULL, + smb2_util_lease_state("R"), 0x11); + status = smb2_create(tree1b, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io2, "R", true, LEASE2, + 0, 0, ls2.lease_epoch); + + /* Verify that we were only sent one break. */ + ls1.lease_epoch += 1; + CHECK_BREAK_INFO_V2(tree1a->session->transport, + "RHW", "RH", LEASE1, ls1.lease_epoch); + + /* again RH over connection 1b doesn't change the epoch */ + ls1.lease_state = smb2_util_lease_state("RH"); + status = smb2_create(tree1b, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io1, "RH", true, LEASE1, + 0, 0, ls1.lease_epoch); + + /* close over connection 1b */ + status = smb2_util_close(tree1b, h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(w); + w.in.file.handle = h; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree1a, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls2.lease_epoch += 1; + CHECK_BREAK_INFO_V2(tree1a->session->transport, + "R", "", LEASE2, ls2.lease_epoch); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(w); + w.in.file.handle = h3; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree1b, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls1.lease_epoch += 1; + CHECK_BREAK_INFO_V2(tree1a->session->transport, + "RH", "", LEASE1, ls1.lease_epoch); + + done: + smb2_util_close(tree1a, h); + smb2_util_close(tree1b, h2); + smb2_util_close(tree1b, h3); + + smb2_util_unlink(tree1a, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_v2_complex2(struct torture_context *tctx, + struct smb2_tree *tree1a) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_request *req2 = NULL; + struct smb2_lease_break_ack ack = {}; + NTSTATUS status; + const char *fname = "lease_v2_complex2.dat"; + bool ret = true; + uint32_t caps; + enum protocol_types protocol; + struct smb2_tree *tree1b = NULL; + struct smbcli_options options1; + + options1 = tree1a->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree1a->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree1a->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + tree1a->session->transport->lease.handler = torture_lease_handler; + tree1a->session->transport->lease.private_data = tree1a; + tree1a->session->transport->oplock.handler = torture_oplock_handler; + tree1a->session->transport->oplock.private_data = tree1a; + + /* create a new connection (same client_guid) */ + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1b)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + tree1b->session->transport->lease.handler = torture_lease_handler; + tree1b->session->transport->lease.private_data = tree1b; + tree1b->session->transport->oplock.handler = torture_oplock_handler; + tree1b->session->transport->oplock.private_data = tree1b; + + smb2_util_unlink(tree1a, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab RWH lease over connection 1a */ + smb2_lease_v2_create(&io1, &ls1, false, fname, LEASE1, NULL, + smb2_util_lease_state("RWH"), 0x4711); + status = smb2_create(tree1a, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io1, "RWH", true, LEASE1, + 0, 0, ls1.lease_epoch); + + /* + * we defer acking the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + /* Ask for RWH on connection 1b, different lease. */ + smb2_lease_v2_create(&io2, &ls2, false, fname, LEASE2, NULL, + smb2_util_lease_state("RWH"), 0x11); + req2 = smb2_create_send(tree1b, &io2); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + + ls1.lease_epoch += 1; + + CHECK_BREAK_INFO_V2(tree1a->session->transport, + "RWH", "RH", LEASE1, ls1.lease_epoch); + + /* Send the break ACK on tree1b. */ + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = SMB2_LEASE_HANDLE|SMB2_LEASE_READ; + + status = smb2_lease_break_ack(tree1b, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE_BREAK_ACK(&ack, "RH", LEASE1); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + status = smb2_create_recv(req2, tctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io2, "RH", true, LEASE2, + 0, 0, ls2.lease_epoch+1); + h2 = io2.out.file.handle; + + done: + smb2_util_close(tree1a, h); + smb2_util_close(tree1b, h2); + + smb2_util_unlink(tree1a, fname); + + talloc_free(mem_ctx); + + return ret; +} + + +static bool test_lease_timeout(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h = {{0}}; + struct smb2_handle hnew = {{0}}; + struct smb2_handle h1b = {{0}}; + NTSTATUS status; + const char *fname = "lease_timeout.dat"; + bool ret = true; + struct smb2_lease_break_ack ack = {}; + struct smb2_request *req2 = NULL; + struct smb2_write w; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname); + + /* Grab a RWH lease. */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + h = io.out.file.handle; + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + * Just don't ack the lease break. + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + /* Break with a RWH request. */ + smb2_lease_create(&io, &ls2, false, fname, LEASE2, smb2_util_lease_state("RWH")); + req2 = smb2_create_send(tree, &io); + torture_assert(tctx, req2 != NULL, "smb2_create_send"); + torture_assert(tctx, req2->state == SMB2_REQUEST_RECV, "req2 pending"); + + CHECK_BREAK_INFO("RWH", "RH", LEASE1); + + /* Copy the break request. */ + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + + /* Now wait for the timeout and get the reply. */ + status = smb2_create_recv(req2, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE2, 0); + hnew = io.out.file.handle; + + /* Ack the break after the timeout... */ + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_UNSUCCESSFUL); + + /* Get state of the original handle. */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE(&io, "", true, LEASE1, 0); + smb2_util_close(tree, io.out.file.handle); + + /* Write on the original handle and make sure it's still valid. */ + torture_reset_lease_break_info(tctx, &lease_break_info); + ZERO_STRUCT(w); + w.in.file.handle = h; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, '1', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Causes new handle to break to NONE. */ + CHECK_BREAK_INFO("RH", "", LEASE2); + + /* Write on the new handle. */ + torture_reset_lease_break_info(tctx, &lease_break_info); + ZERO_STRUCT(w); + w.in.file.handle = hnew; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 1024); + memset(w.in.data.data, '2', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + /* No break - original handle was already NONE. */ + CHECK_NO_BREAK(tctx); + smb2_util_close(tree, hnew); + + /* Upgrade to R on LEASE1. */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE(&io, "R", true, LEASE1, 0); + h1b = io.out.file.handle; + smb2_util_close(tree, h1b); + + /* Upgrade to RWH on LEASE1. */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + h1b = io.out.file.handle; + smb2_util_close(tree, h1b); + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, hnew); + smb2_util_close(tree, h1b); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_rename_wait(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_lease ls3; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + union smb_setfileinfo sinfo; + NTSTATUS status; + const char *fname_src = "lease_rename_src.dat"; + const char *fname_dst = "lease_rename_dst.dat"; + bool ret = true; + struct smb2_lease_break_ack ack = {}; + struct smb2_request *rename_req = NULL; + uint32_t caps; + unsigned int i; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree, fname_src); + smb2_util_unlink(tree, fname_dst); + + /* Short timeout for fails. */ + tree->session->transport->options.request_timeout = 15; + + /* Grab a RH lease. */ + smb2_lease_create(&io, + &ls1, + false, + fname_src, + LEASE1, + smb2_util_lease_state("RH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE1, 0); + h1 = io.out.file.handle; + + /* Second open with a RH lease. */ + smb2_lease_create(&io, + &ls2, + false, + fname_src, + LEASE2, + smb2_util_lease_state("RH")); + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.desired_access = GENERIC_READ_ACCESS; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE2, 0); + h2 = io.out.file.handle; + + /* + * Don't ack a lease break. + */ + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + /* Break with a rename. */ + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.new_name = fname_dst; + rename_req = smb2_setinfo_file_send(tree, &sinfo); + + torture_assert(tctx, + rename_req != NULL, + "smb2_setinfo_file_send"); + torture_assert(tctx, + rename_req->state == SMB2_REQUEST_RECV, + "rename pending"); + + /* Try and open the destination with a RH lease. */ + smb2_lease_create(&io, + &ls3, + false, + fname_dst, + LEASE3, + smb2_util_lease_state("RH")); + /* We want to open, not create. */ + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.desired_access = GENERIC_READ_ACCESS; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* + * The smb2_create() I/O should have picked up the break request + * caused by the pending rename. + */ + + /* Copy the break request. */ + ack.in.lease.lease_key = + lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = + lease_break_info.lease_break.new_lease_state; + + /* + * Give the server 3 more chances to have renamed + * the file. Better than doing a sleep. + */ + for (i = 0; i < 3; i++) { + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + } + + /* Ack the break. The server is now free to rename. */ + status = smb2_lease_break_ack(tree, &ack); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Get the rename reply. */ + status = smb2_setinfo_recv(rename_req); + CHECK_STATUS(status, NT_STATUS_OK); + + /* The target should now exist. */ + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + + done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + smb2_util_unlink(tree, fname_src); + smb2_util_unlink(tree, fname_dst); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_v2_rename(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h = {{0}}; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + union smb_setfileinfo sinfo; + const char *fname = "lease_v2_rename_src.dat"; + const char *fname_dst = "lease_v2_rename_dst.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname_dst); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RHW"), + 0x4711); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1.lease_epoch); + + /* Now rename - what happens ? */ + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.new_name = fname_dst; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* No lease break. */ + CHECK_NO_BREAK(tctx); + + /* Check we can open another handle on the new name. */ + smb2_lease_v2_create_share(&io, &ls1, false, fname_dst, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state(""), + ls1.lease_epoch); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1.lease_epoch); + smb2_util_close(tree, h1); + + /* Try another lease key. */ + smb2_lease_v2_create_share(&io, &ls2, false, fname_dst, + smb2_util_share_access("RWD"), + LEASE2, NULL, + smb2_util_lease_state("RWH"), + 0x44); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, LEASE2, 0, 0, ls2.lease_epoch ); + CHECK_BREAK_INFO_V2(tree->session->transport, + "RWH", "RH", LEASE1, ls1.lease_epoch + 1); + ls1.lease_epoch += 1; + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Now rename back. */ + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.new_name = fname; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Breaks to R on LEASE2. */ + CHECK_BREAK_INFO_V2(tree->session->transport, + "RH", "R", LEASE2, ls2.lease_epoch + 1); + ls2.lease_epoch += 1; + + /* Check we can open another handle on the current name. */ + smb2_lease_v2_create_share(&io, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state(""), + ls1.lease_epoch); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RH", true, LEASE1, 0, 0, ls1.lease_epoch); + smb2_util_close(tree, h1); + +done: + + smb2_util_close(tree, h); + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname_dst); + + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + + +static bool test_lease_dynamic_share(struct torture_context *tctx, + struct smb2_tree *tree1a) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1; + struct smb2_handle h, h1, h2; + struct smb2_write w; + NTSTATUS status; + const char *fname = "dynamic_path.dat"; + bool ret = true; + uint32_t caps; + struct smb2_tree *tree_2 = NULL; + struct smb2_tree *tree_3 = NULL; + struct smbcli_options options; + const char *orig_share = NULL; + + if (!TARGET_IS_SAMBA3(tctx)) { + torture_skip(tctx, "dynamic shares are not supported"); + return true; + } + + options = tree1a->session->transport->options; + options.client_guid = GUID_random(); + + caps = smb2cli_conn_server_capabilities(tree1a->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* + * Save off original share name and change it to dynamic_share. + * This must have been pre-created with a dynamic path containing + * %t. It means we'll sleep between the connects in order to + * get a different timestamp for the share path. + */ + + orig_share = lpcfg_parm_string(tctx->lp_ctx, NULL, "torture", "share"); + orig_share = talloc_strdup(tctx->lp_ctx, orig_share); + if (orig_share == NULL) { + torture_result(tctx, TORTURE_FAIL, __location__ "no memory\n"); + ret = false; + goto done; + } + lpcfg_set_cmdline(tctx->lp_ctx, "torture:share", "dynamic_share"); + + /* create a new connection (same client_guid) */ + sleep(2); + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree_2)) { + torture_result(tctx, TORTURE_FAIL, + __location__ "couldn't reconnect " + "max protocol 2.1, bailing\n"); + ret = false; + goto done; + } + + tree_2->session->transport->lease.handler = torture_lease_handler; + tree_2->session->transport->lease.private_data = tree_2; + tree_2->session->transport->oplock.handler = torture_oplock_handler; + tree_2->session->transport->oplock.private_data = tree_2; + + smb2_util_unlink(tree_2, fname); + + /* create a new connection (same client_guid) */ + sleep(2); + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree_3)) { + torture_result(tctx, TORTURE_FAIL, + __location__ "couldn't reconnect " + "max protocol 3.0, bailing\n"); + ret = false; + goto done; + } + + tree_3->session->transport->lease.handler = torture_lease_handler; + tree_3->session->transport->lease.private_data = tree_3; + tree_3->session->transport->oplock.handler = torture_oplock_handler; + tree_3->session->transport->oplock.private_data = tree_3; + + smb2_util_unlink(tree_3, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Get RWH lease over connection 2 */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree_2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + h = io.out.file.handle; + + /* Write some data into it. */ + w.in.file.handle = h; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, '1', w.in.data.length); + status = smb2_write(tree_2, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Open the same name over connection 3. */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree_3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + /* h1 should have replied with NONE. */ + CHECK_LEASE(&io, "", true, LEASE1, 0); + + /* We should have broken h to NONE. */ + CHECK_BREAK_INFO("RWH", "", LEASE1); + + /* Try to upgrade to RWH over connection 2 */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree_2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_VAL(io.out.size, 4096); + CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); + /* Should have been denied. */ + CHECK_LEASE(&io, "", true, LEASE1, 0); + smb2_util_close(tree_2, h2); + + /* Try to upgrade to RWH over connection 3 */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree_3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_VAL(io.out.size, 0); + CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); + /* Should have been denied. */ + CHECK_LEASE(&io, "", true, LEASE1, 0); + smb2_util_close(tree_3, h2); + + /* Write some data into it. */ + w.in.file.handle = h1; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 1024); + memset(w.in.data.data, '2', w.in.data.length); + status = smb2_write(tree_3, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Close everything.. */ + smb2_util_close(tree_2, h); + smb2_util_close(tree_3, h1); + + /* And ensure we can get a lease ! */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree_2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + h = io.out.file.handle; + /* And the file is the right size. */ + CHECK_VAL(io.out.size, 4096); \ + /* Close it. */ + smb2_util_close(tree_2, h); + + /* And ensure we can get a lease ! */ + smb2_lease_create(&io, &ls1, false, fname, LEASE1, smb2_util_lease_state("RWH")); + status = smb2_create(tree_3, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + h = io.out.file.handle; + /* And the file is the right size. */ + CHECK_VAL(io.out.size, 1024); \ + /* Close it. */ + smb2_util_close(tree_3, h); + + done: + + if (tree_2 != NULL) { + smb2_util_close(tree_2, h); + smb2_util_unlink(tree_2, fname); + } + if (tree_3 != NULL) { + smb2_util_close(tree_3, h1); + smb2_util_close(tree_3, h2); + + smb2_util_unlink(tree_3, fname); + } + + /* Set sharename back. */ + lpcfg_set_cmdline(tctx->lp_ctx, "torture:share", orig_share); + + talloc_free(mem_ctx); + + return ret; +} + +/* + * Test identifies a bug where the Samba server will not trigger a lease break + * for a handle caching lease held by a client when the underlying file is + * deleted. + * Test: + * Connect session2. + * open file in session1 + * session1 should have RWH lease. + * open file in session2 + * lease break sent to session1 to downgrade lease to RH + * close file in session 2 + * unlink file in session 2 + * lease break sent to session1 to downgrade lease to R + * Cleanup + */ +static bool test_lease_unlink(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + bool ret = true; + struct smbcli_options transport2_options; + struct smb2_tree *tree2 = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2; + struct smb2_handle h1 = {{ 0 }}; + struct smb2_handle h2 = {{ 0 }}; + const char *fname = "lease_unlink.dat"; + uint32_t caps; + struct smb2_create io1; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_lease ls2; + + caps = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* Connect 2nd connection */ + transport2_options = transport1->options; + transport2_options.client_guid = GUID_random(); + if (!torture_smb2_connection_ext(tctx, 0, &transport2_options, &tree2)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + return false; + } + transport2 = tree2->session->transport; + + /* Set lease handlers */ + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + transport2->lease.handler = torture_lease_handler; + transport2->lease.private_data = tree2; + + + smb2_lease_create(&io1, &ls1, false, fname, LEASE1, + smb2_util_lease_state("RHW")); + smb2_lease_create(&io2, &ls2, false, fname, LEASE2, + smb2_util_lease_state("RHW")); + + smb2_util_unlink(tree1, fname); + + torture_comment(tctx, "Client opens fname with session 1\n"); + torture_reset_lease_break_info(tctx, &lease_break_info); + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RHW", true, LEASE1, 0); + CHECK_VAL(lease_break_info.count, 0); + + torture_comment(tctx, "Client opens fname with session 2\n"); + torture_reset_lease_break_info(tctx, &lease_break_info); + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "RH", true, LEASE2, 0); + CHECK_VAL(lease_break_info.count, 1); + CHECK_BREAK_INFO("RHW", "RH", LEASE1); + + torture_comment(tctx, + "Client closes and then unlinks fname with session 2\n"); + torture_reset_lease_break_info(tctx, &lease_break_info); + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + CHECK_VAL(lease_break_info.count, 1); + CHECK_BREAK_INFO("RH", "R", LEASE1); + +done: + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_unlink(tree1, fname); + + return ret; +} + +static bool test_lease_timeout_disconnect(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + bool ret = true; + struct smbcli_options transport2_options; + struct smbcli_options transport3_options; + struct smb2_tree *tree2 = NULL; + struct smb2_tree *tree3 = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2; + struct smb2_transport *transport3; + const char *fname = "lease_timeout_logoff.dat" ; + uint32_t caps; + struct smb2_create io1; + struct smb2_create io2; + struct smb2_request *req2 = NULL; + struct smb2_lease ls1; + + caps = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + smb2_util_unlink(tree1, fname); + + /* Connect 2nd connection */ + torture_comment(tctx, "connect tree2 with the same client_guid\n"); + transport2_options = transport1->options; + if (!torture_smb2_connection_ext(tctx, 0, &transport2_options, &tree2)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + return false; + } + transport2 = tree2->session->transport; + + /* Connect 3rd connection */ + torture_comment(tctx, "connect tree3 with the same client_guid\n"); + transport3_options = transport1->options; + if (!torture_smb2_connection_ext(tctx, 0, &transport3_options, &tree3)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + return false; + } + transport3 = tree3->session->transport; + + /* Set lease handlers */ + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + transport2->lease.handler = torture_lease_handler; + transport2->lease.private_data = tree2; + transport3->lease.handler = torture_lease_handler; + transport3->lease.private_data = tree3; + + smb2_lease_create_share(&io1, &ls1, false, fname, + smb2_util_share_access(""), + LEASE1, + smb2_util_lease_state("RH")); + io1.in.durable_open = true; + smb2_generic_create(&io2, NULL, false, fname, + NTCREATEX_DISP_OPEN_IF, + SMB2_OPLOCK_LEVEL_NONE, 0, 0); + + torture_comment(tctx, "tree1: create file[%s] with durable RH lease (SHARE NONE)\n", fname); + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RH", true, LEASE1, 0); + CHECK_VAL(lease_break_info.count, 0); + + torture_comment(tctx, "tree1: skip lease acks\n"); + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + torture_comment(tctx, "tree2: open file[%s] without lease (SHARE RWD)\n", fname); + req2 = smb2_create_send(tree2, &io2); + torture_assert(tctx, req2 != NULL, "req2 started"); + + torture_comment(tctx, "tree1: wait for lease break\n"); + torture_wait_for_lease_break(tctx); + CHECK_VAL(lease_break_info.count, 1); + CHECK_BREAK_INFO("RH", "R", LEASE1); + + torture_comment(tctx, "tree1: reset lease handler\n"); + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + CHECK_VAL(lease_break_info.count, 0); + + torture_comment(tctx, "tree2: check for SMB2_REQUEST_RECV\n"); + torture_assert_int_equal(tctx, req2->state, + SMB2_REQUEST_RECV, + "SMB2_REQUEST_RECV"); + + torture_comment(tctx, "sleep 1\n"); + smb_msleep(1000); + + torture_comment(tctx, "transport1: keepalive\n"); + status = smb2_keepalive(transport1); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "transport2: keepalive\n"); + status = smb2_keepalive(transport2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "transport3: keepalive\n"); + status = smb2_keepalive(transport3); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "tree2: check for SMB2_REQUEST_RECV\n"); + torture_assert_int_equal(tctx, req2->state, + SMB2_REQUEST_RECV, + "SMB2_REQUEST_RECV"); + torture_comment(tctx, "tree2: check for STATUS_PENDING\n"); + torture_assert(tctx, req2->cancel.can_cancel, "STATUS_PENDING"); + + torture_comment(tctx, "sleep 1\n"); + smb_msleep(1000); + torture_comment(tctx, "transport1: keepalive\n"); + status = smb2_keepalive(transport1); + CHECK_STATUS(status, NT_STATUS_OK); + torture_comment(tctx, "transport2: disconnect\n"); + TALLOC_FREE(tree2); + + torture_comment(tctx, "sleep 1\n"); + smb_msleep(1000); + torture_comment(tctx, "transport1: keepalive\n"); + status = smb2_keepalive(transport1); + CHECK_STATUS(status, NT_STATUS_OK); + torture_comment(tctx, "transport1: disconnect\n"); + TALLOC_FREE(tree1); + + torture_comment(tctx, "sleep 1\n"); + smb_msleep(1000); + torture_comment(tctx, "transport3: keepalive\n"); + status = smb2_keepalive(transport3); + CHECK_STATUS(status, NT_STATUS_OK); + torture_comment(tctx, "transport3: disconnect\n"); + TALLOC_FREE(tree3); + +done: + + return ret; +} + +static bool test_lease_duplicate_create(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + const char *fname1 = "duplicate_create1.dat"; + const char *fname2 = "duplicate_create2.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* Ensure files don't exist. */ + smb2_util_unlink(tree, fname1); + smb2_util_unlink(tree, fname2); + + /* Create file1 - LEASE1 key. */ + smb2_lease_create(&io, &ls, false, fname1, LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + + /* + * Create file2 with the same LEASE1 key - this should fail with. + * INVALID_PARAMETER. + */ + smb2_lease_create(&io, &ls, false, fname2, LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + smb2_util_close(tree, h1); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname1); + smb2_util_unlink(tree, fname2); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_duplicate_open(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + const char *fname1 = "duplicate_open1.dat"; + const char *fname2 = "duplicate_open2.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* Ensure files don't exist. */ + smb2_util_unlink(tree, fname1); + smb2_util_unlink(tree, fname2); + + /* Create file1 - LEASE1 key. */ + smb2_lease_create(&io, &ls, false, fname1, LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RWH", true, LEASE1, 0); + + /* Leave file1 open and leased. */ + + /* Create file2 - no lease. */ + smb2_lease_create(&io, NULL, false, fname2, 0, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + /* Close it. */ + smb2_util_close(tree, h2); + + /* + * Try and open file2 with the same LEASE1 key - this should fail with. + * INVALID_PARAMETER. + */ + smb2_lease_create(&io, &ls, false, fname2, LEASE1, + smb2_util_lease_state("RWH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + /* + * If we did open this is an error, but save off + * the handle so we close below. + */ + h2 = io.out.file.handle; + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname1); + smb2_util_unlink(tree, fname2); + talloc_free(mem_ctx); + return ret; +} + +static bool test_lease_v1_bug_15148(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_write w; + NTSTATUS status; + const char *fname = "lease_v1_bug_15148.dat"; + bool ret = true; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + smb2_util_unlink(tree, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab R lease over connection 1a */ + smb2_lease_create(&io1, &ls1, false, fname, LEASE1, smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "R", true, LEASE1, 0); + + CHECK_NO_BREAK(tctx); + + /* Contend with LEASE2. */ + smb2_lease_create(&io2, &ls2, false, fname, LEASE2, smb2_util_lease_state("R")); + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "R", true, LEASE2, 0); + + CHECK_NO_BREAK(tctx); + + ZERO_STRUCT(w); + w.in.file.handle = h1; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls2.lease_epoch += 1; + CHECK_BREAK_INFO("R", "", LEASE2); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(w); + w.in.file.handle = h1; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'O', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_NO_BREAK(tctx); + + ZERO_STRUCT(w); + w.in.file.handle = h2; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls1.lease_epoch += 1; + CHECK_BREAK_INFO("R", "", LEASE1); + + done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_v2_bug_15148(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_write w; + NTSTATUS status; + const char *fname = "lease_v2_bug_15148.dat"; + bool ret = true; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + smb2_util_unlink(tree, fname); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Grab R lease over connection 1a */ + smb2_lease_v2_create(&io1, &ls1, false, fname, LEASE1, NULL, + smb2_util_lease_state("R"), 0x4711); + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io1, "R", true, LEASE1, + 0, 0, ls1.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* Contend with LEASE2. */ + smb2_lease_v2_create(&io2, &ls2, false, fname, LEASE2, NULL, + smb2_util_lease_state("R"), 0x11); + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io2, "R", true, LEASE2, + 0, 0, ls2.lease_epoch); + + CHECK_NO_BREAK(tctx); + + ZERO_STRUCT(w); + w.in.file.handle = h1; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls2.lease_epoch += 1; + CHECK_BREAK_INFO_V2(tree->session->transport, + "R", "", LEASE2, ls2.lease_epoch); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + ZERO_STRUCT(w); + w.in.file.handle = h1; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'O', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_NO_BREAK(tctx); + + ZERO_STRUCT(w); + w.in.file.handle = h2; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + memset(w.in.data.data, 'o', w.in.data.length); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + ls1.lease_epoch += 1; + CHECK_BREAK_INFO_V2(tree->session->transport, + "R", "", LEASE1, ls1.lease_epoch); + + done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +struct torture_suite *torture_smb2_lease_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "lease"); + + torture_suite_add_1smb2_test(suite, "request", test_lease_request); + torture_suite_add_1smb2_test(suite, "break_twice", + test_lease_break_twice); + torture_suite_add_1smb2_test(suite, "nobreakself", + test_lease_nobreakself); + torture_suite_add_1smb2_test(suite, "statopen", test_lease_statopen); + torture_suite_add_1smb2_test(suite, "statopen2", test_lease_statopen2); + torture_suite_add_1smb2_test(suite, "statopen3", test_lease_statopen3); + torture_suite_add_1smb2_test(suite, "statopen4", test_lease_statopen4); + torture_suite_add_1smb2_test(suite, "upgrade", test_lease_upgrade); + torture_suite_add_1smb2_test(suite, "upgrade2", test_lease_upgrade2); + torture_suite_add_1smb2_test(suite, "upgrade3", test_lease_upgrade3); + torture_suite_add_1smb2_test(suite, "break", test_lease_break); + torture_suite_add_1smb2_test(suite, "oplock", test_lease_oplock); + torture_suite_add_1smb2_test(suite, "multibreak", test_lease_multibreak); + torture_suite_add_1smb2_test(suite, "breaking1", test_lease_breaking1); + torture_suite_add_1smb2_test(suite, "breaking2", test_lease_breaking2); + torture_suite_add_1smb2_test(suite, "breaking3", test_lease_breaking3); + torture_suite_add_1smb2_test(suite, "v2_breaking3", test_lease_v2_breaking3); + torture_suite_add_1smb2_test(suite, "breaking4", test_lease_breaking4); + torture_suite_add_1smb2_test(suite, "breaking5", test_lease_breaking5); + torture_suite_add_1smb2_test(suite, "breaking6", test_lease_breaking6); + torture_suite_add_2smb2_test(suite, "lock1", test_lease_lock1); + torture_suite_add_1smb2_test(suite, "complex1", test_lease_complex1); + torture_suite_add_1smb2_test(suite, "v2_request_parent", + test_lease_v2_request_parent); + torture_suite_add_1smb2_test(suite, "v2_request", test_lease_v2_request); + torture_suite_add_1smb2_test(suite, "v2_epoch1", test_lease_v2_epoch1); + torture_suite_add_1smb2_test(suite, "v2_epoch2", test_lease_v2_epoch2); + torture_suite_add_1smb2_test(suite, "v2_epoch3", test_lease_v2_epoch3); + torture_suite_add_1smb2_test(suite, "v2_complex1", test_lease_v2_complex1); + torture_suite_add_1smb2_test(suite, "v2_complex2", test_lease_v2_complex2); + torture_suite_add_1smb2_test(suite, "v2_rename", test_lease_v2_rename); + torture_suite_add_1smb2_test(suite, "dynamic_share", test_lease_dynamic_share); + torture_suite_add_1smb2_test(suite, "timeout", test_lease_timeout); + torture_suite_add_1smb2_test(suite, "unlink", test_lease_unlink); + torture_suite_add_1smb2_test(suite, "timeout-disconnect", test_lease_timeout_disconnect); + torture_suite_add_1smb2_test(suite, "rename_wait", + test_lease_rename_wait); + torture_suite_add_1smb2_test(suite, "duplicate_create", + test_lease_duplicate_create); + torture_suite_add_1smb2_test(suite, "duplicate_open", + test_lease_duplicate_open); + torture_suite_add_1smb2_test(suite, "v1_bug15148", + test_lease_v1_bug_15148); + torture_suite_add_1smb2_test(suite, "v2_bug15148", + test_lease_v2_bug_15148); + + suite->description = talloc_strdup(suite, "SMB2-LEASE tests"); + + return suite; +} diff --git a/source4/torture/smb2/lease_break_handler.c b/source4/torture/smb2/lease_break_handler.c new file mode 100644 index 0000000..6c865dc --- /dev/null +++ b/source4/torture/smb2/lease_break_handler.c @@ -0,0 +1,161 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 leases + + Copyright (C) Zachary Loafman 2009 + + 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/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/util.h" +#include "libcli/smb/smbXcli_base.h" +#include "lease_break_handler.h" + +struct lease_break_info lease_break_info; + +void torture_lease_break_callback(struct smb2_request *req) +{ + NTSTATUS status; + + status = smb2_lease_break_ack_recv(req, &lease_break_info.lease_break_ack); + if (!NT_STATUS_IS_OK(status)) + lease_break_info.failures++; + + return; +} + +/* a lease break request handler */ +bool torture_lease_handler(struct smb2_transport *transport, + const struct smb2_lease_break *lb, + void *private_data) +{ + struct smb2_tree *tree = private_data; + struct smb2_lease_break_ack io; + struct smb2_request *req; + const char *action = NULL; + char *ls = smb2_util_lease_state_string(lease_break_info.tctx, + lb->new_lease_state); + + if (lb->break_flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) { + action = "acking"; + } else { + action = "received"; + } + + lease_break_info.lease_transport = transport; + lease_break_info.lease_break = *lb; + lease_break_info.count++; + + if (lease_break_info.lease_skip_ack) { + torture_comment(lease_break_info.tctx, + "transport[%p] Skip %s to %s in lease handler\n", + transport, action, ls); + return true; + } + + torture_comment(lease_break_info.tctx, + "transport[%p] %s to %s in lease handler\n", + transport, action, ls); + + if (lb->break_flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) { + ZERO_STRUCT(io); + io.in.lease.lease_key = lb->current_lease.lease_key; + io.in.lease.lease_state = lb->new_lease_state; + + req = smb2_lease_break_ack_send(tree, &io); + req->async.fn = torture_lease_break_callback; + req->async.private_data = NULL; + } + + return true; +} + +/* + * A lease break handler which ignores incoming lease break requests + * To be used in cases where the client is expected to ignore incoming + * lease break requests + */ +bool torture_lease_ignore_handler(struct smb2_transport *transport, + const struct smb2_lease_break *lb, + void *private_data) +{ + return true; +} + +/* + Timer handler function notifies the registering function that time is up +*/ +static void timeout_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + bool *timesup = (bool *)private_data; + *timesup = true; + return; +} + +/* + Wait a short period of time to receive a single oplock break request +*/ +void torture_wait_for_lease_break(struct torture_context *tctx) +{ + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + struct tevent_timer *te = NULL; + struct timeval ne; + bool timesup = false; + int old_count = lease_break_info.count; + + /* Wait 1 second for an lease break */ + ne = tevent_timeval_current_ofs(0, 1000000); + + te = tevent_add_timer(tctx->ev, tmp_ctx, ne, timeout_cb, ×up); + if (te == NULL) { + torture_comment(tctx, "Failed to wait for an lease break. " + "test results may not be accurate.\n"); + goto done; + } + + torture_comment(tctx, "Waiting for a potential lease break...\n"); + while (!timesup && lease_break_info.count < old_count + 1) { + if (tevent_loop_once(tctx->ev) != 0) { + torture_comment(tctx, "Failed to wait for a lease " + "break. test results may not be " + "accurate.\n"); + goto done; + } + } + if (timesup) { + torture_comment(tctx, "... waiting for a lease break timed out\n"); + } else { + torture_comment(tctx, "Got %u lease breaks\n", + lease_break_info.count - old_count); + } + +done: + /* We don't know if the timed event fired and was freed, we received + * our oplock break, or some other event triggered the loop. Thus, + * we create a tmp_ctx to be able to safely free/remove the timed + * event in all 3 cases. */ + talloc_free(tmp_ctx); + + return; +} diff --git a/source4/torture/smb2/lease_break_handler.h b/source4/torture/smb2/lease_break_handler.h new file mode 100644 index 0000000..90fde1a --- /dev/null +++ b/source4/torture/smb2/lease_break_handler.h @@ -0,0 +1,134 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 leases + + Copyright (C) Zachary Loafman 2009 + + 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/>. +*/ + +#include "torture/util.h" + +struct lease_break_info { + struct torture_context *tctx; + + struct smb2_lease_break lease_break; + struct smb2_transport *lease_transport; + bool lease_skip_ack; + struct smb2_lease_break_ack lease_break_ack; + int count; + int failures; + + struct smb2_handle oplock_handle; + uint8_t held_oplock_level; + uint8_t oplock_level; + int oplock_count; + int oplock_failures; +}; + +#define CHECK_LEASE_BREAK(__lb, __oldstate, __state, __key) \ + do { \ + uint16_t __new = smb2_util_lease_state(__state); \ + uint16_t __old = smb2_util_lease_state(__oldstate); \ + CHECK_VAL((__lb)->new_lease_state, __new); \ + CHECK_VAL((__lb)->current_lease.lease_state, __old); \ + CHECK_VAL((__lb)->current_lease.lease_key.data[0], (__key)); \ + CHECK_VAL((__lb)->current_lease.lease_key.data[1], ~(__key)); \ + if (__old & (SMB2_LEASE_WRITE | SMB2_LEASE_HANDLE)) { \ + CHECK_VAL((__lb)->break_flags, \ + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); \ + } else { \ + CHECK_VAL((__lb)->break_flags, 0); \ + } \ + } while(0) + +#define CHECK_LEASE_BREAK_ACK(__lba, __state, __key) \ + do { \ + CHECK_VAL((__lba)->out.reserved, 0); \ + CHECK_VAL((__lba)->out.lease.lease_key.data[0], (__key)); \ + CHECK_VAL((__lba)->out.lease.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__lba)->out.lease.lease_state, smb2_util_lease_state(__state)); \ + CHECK_VAL((__lba)->out.lease.lease_flags, 0); \ + CHECK_VAL((__lba)->out.lease.lease_duration, 0); \ + } while(0) + +#define CHECK_NO_BREAK(tctx) \ + do { \ + torture_wait_for_lease_break(tctx); \ + CHECK_VAL(lease_break_info.failures, 0); \ + CHECK_VAL(lease_break_info.count, 0); \ + CHECK_VAL(lease_break_info.oplock_failures, 0); \ + CHECK_VAL(lease_break_info.oplock_count, 0); \ + } while(0) + +#define CHECK_OPLOCK_BREAK(__brokento) \ + do { \ + torture_wait_for_lease_break(tctx); \ + CHECK_VAL(lease_break_info.oplock_count, 1); \ + CHECK_VAL(lease_break_info.oplock_failures, 0); \ + CHECK_VAL(lease_break_info.oplock_level, \ + smb2_util_oplock_level(__brokento)); \ + lease_break_info.held_oplock_level = lease_break_info.oplock_level; \ + } while(0) + +#define _CHECK_BREAK_INFO(__oldstate, __state, __key) \ + do { \ + torture_wait_for_lease_break(tctx); \ + CHECK_VAL(lease_break_info.failures, 0); \ + CHECK_VAL(lease_break_info.count, 1); \ + CHECK_LEASE_BREAK(&lease_break_info.lease_break, (__oldstate), \ + (__state), (__key)); \ + if (!lease_break_info.lease_skip_ack && \ + (lease_break_info.lease_break.break_flags & \ + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED)) \ + { \ + torture_wait_for_lease_break(tctx); \ + CHECK_LEASE_BREAK_ACK(&lease_break_info.lease_break_ack, \ + (__state), (__key)); \ + } \ + } while(0) + +#define CHECK_BREAK_INFO(__oldstate, __state, __key) \ + do { \ + _CHECK_BREAK_INFO(__oldstate, __state, __key); \ + CHECK_VAL(lease_break_info.lease_break.new_epoch, 0); \ + } while(0) + +#define CHECK_BREAK_INFO_V2(__transport, __oldstate, __state, __key, __epoch) \ + do { \ + _CHECK_BREAK_INFO(__oldstate, __state, __key); \ + CHECK_VAL(lease_break_info.lease_break.new_epoch, __epoch); \ + if (!TARGET_IS_SAMBA3(tctx)) { \ + CHECK_VAL((uintptr_t)lease_break_info.lease_transport, \ + (uintptr_t)__transport); \ + } \ + } while(0) + +extern struct lease_break_info lease_break_info; + +bool torture_lease_handler(struct smb2_transport *transport, + const struct smb2_lease_break *lb, + void *private_data); +bool torture_lease_ignore_handler(struct smb2_transport *transport, + const struct smb2_lease_break *lb, + void *private_data); +void torture_wait_for_lease_break(struct torture_context *tctx); + +static inline void torture_reset_lease_break_info(struct torture_context *tctx, + struct lease_break_info *r) +{ + ZERO_STRUCTP(r); + r->tctx = tctx; +} diff --git a/source4/torture/smb2/lock.c b/source4/torture/smb2/lock.c new file mode 100644 index 0000000..eac0d55 --- /dev/null +++ b/source4/torture/smb2/lock.c @@ -0,0 +1,3513 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 lock test suite + + Copyright (C) Stefan Metzmacher 2006 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/util.h" + +#include "lib/events/events.h" +#include "param/param.h" + +#define CHECK_STATUS(status, correct) do { \ + const char *_cmt = "(" __location__ ")"; \ + torture_assert_ntstatus_equal_goto(torture,status,correct, \ + ret,done,_cmt); \ + } while (0) + +#define CHECK_STATUS_CMT(status, correct, cmt) do { \ + torture_assert_ntstatus_equal_goto(torture,status,correct, \ + ret,done,cmt); \ + } while (0) + +#define CHECK_STATUS_CONT(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(torture, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + const char *_cmt = "(" __location__ ")"; \ + torture_assert_int_equal_goto(torture,v,correct,ret,done,_cmt); \ + } while (0) + +#define BASEDIR "testlock" + +#define TARGET_SUPPORTS_INVALID_LOCK_RANGE(_tctx) \ + (torture_setting_bool(_tctx, "invalid_lock_range_support", true)) +#define TARGET_IS_W2K8(_tctx) (torture_setting_bool(_tctx, "w2k8", false)) + +#define WAIT_FOR_ASYNC_RESPONSE(req) \ + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { \ + if (tevent_loop_once(torture->ev) != 0) { \ + break; \ + } \ + } + +static bool test_valid_request(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + + ZERO_STRUCT(buf); + + status = torture_smb2_testfile(tree, "lock1.txt", &h); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = el; + + torture_comment(torture, "Test request with 0 locks.\n"); + + lck.in.lock_count = 0x0000; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0x0000000000000000; + el[0].length = 0x0000000000000000; + el[0].reserved = 0x0000000000000000; + el[0].flags = 0x00000000; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + lck.in.lock_count = 0x0000; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 0; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_SHARED; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 0; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_NONE; + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has bug validating lock flags " + "parameter.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + } + + torture_comment(torture, "Test >63-bit lock requests.\n"); + + lck.in.file.handle.data[0] +=1; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + lck.in.file.handle.data[0] -=1; + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x123ab1; + lck.in.file.handle = h; + el[0].offset = UINT64_MAX; + el[0].length = UINT64_MAX; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + if (TARGET_SUPPORTS_INVALID_LOCK_RANGE(torture)) { + CHECK_STATUS(status, NT_STATUS_INVALID_LOCK_RANGE); + } else { + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + } + + lck.in.lock_sequence = 0x123ab2; + status = smb2_lock(tree, &lck); + if (TARGET_SUPPORTS_INVALID_LOCK_RANGE(torture)) { + CHECK_STATUS(status, NT_STATUS_INVALID_LOCK_RANGE); + } else { + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + } + + torture_comment(torture, "Test basic lock stacking.\n"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x12345678; + lck.in.file.handle = h; + el[0].offset = UINT32_MAX; + el[0].length = UINT32_MAX; + el[0].reserved = 0x87654321; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + el[0].flags = SMB2_LOCK_FLAG_SHARED; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x87654321; + lck.in.file.handle = h; + el[0].offset = 0x00000000FFFFFFFF; + el[0].length = 0x00000000FFFFFFFF; + el[0].reserved = 0x1234567; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x1234567; + lck.in.file.handle = h; + el[0].offset = 0x00000000FFFFFFFF; + el[0].length = 0x00000000FFFFFFFF; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Test flags field permutations.\n"); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0; + lck.in.file.handle = h; + el[0].offset = 1; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = ~SMB2_LOCK_FLAG_ALL_MASK; + + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has bug validating lock flags " + "parameter.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + } + + if (TARGET_IS_W2K8(torture)) { + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK | + SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK | + SMB2_LOCK_FLAG_SHARED; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + torture_warning(torture, "Target has bug validating lock flags " + "parameter.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + } + + torture_comment(torture, "Test return error when 2 locks are " + "requested\n"); + + lck.in.lock_count = 2; + lck.in.lock_sequence = 0; + lck.in.file.handle = h; + el[0].offset = 9999; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[1].offset = 9999; + el[1].length = 1; + el[1].reserved = 0x00000000; + + lck.in.lock_count = 2; + el[0].flags = 0; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + lck.in.lock_count = 2; + el[0].flags = SMB2_LOCK_FLAG_SHARED|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[1].flags = SMB2_LOCK_FLAG_SHARED; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + lck.in.lock_count = 2; + el[0].flags = 0; + el[1].flags = 0; + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has bug validating lock flags " + "parameter.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + } + + lck.in.lock_count = 2; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = 0; + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has bug validating lock flags " + "parameter.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + } + + lck.in.lock_count = 1; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + lck.in.lock_count = 1; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + lck.in.lock_count = 1; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + lck.in.lock_count = 1; + el[0].flags = SMB2_LOCK_FLAG_SHARED; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.lock_count = 2; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.lock_count = 1; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + +done: + return ret; +} + +struct test_lock_read_write_state { + const char *fname; + uint32_t lock_flags; + NTSTATUS write_h1_status; + NTSTATUS read_h1_status; + NTSTATUS write_h2_status; + NTSTATUS read_h2_status; +}; + +static bool test_lock_read_write(struct torture_context *torture, + struct smb2_tree *tree, + struct test_lock_read_write_state *s) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h1, h2; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_create cr; + struct smb2_write wr; + struct smb2_read rd; + struct smb2_lock_element el[1]; + + lck.in.locks = el; + + ZERO_STRUCT(buf); + + status = torture_smb2_testfile(tree, s->fname, &h1); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, h1, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h1; + el[0].offset = 0; + el[0].length = ARRAY_SIZE(buf)/2; + el[0].reserved = 0x00000000; + el[0].flags = s->lock_flags; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h1; + el[0].offset = ARRAY_SIZE(buf)/2; + el[0].length = ARRAY_SIZE(buf)/2; + el[0].reserved = 0x00000000; + el[0].flags = s->lock_flags; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + ZERO_STRUCT(cr); + cr.in.oplock_level = 0; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.create_options = 0; + cr.in.fname = s->fname; + + status = smb2_create(tree, tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + h2 = cr.out.file.handle; + + ZERO_STRUCT(wr); + wr.in.file.handle = h1; + wr.in.offset = ARRAY_SIZE(buf)/2; + wr.in.data = data_blob_const(buf, ARRAY_SIZE(buf)/2); + + status = smb2_write(tree, &wr); + CHECK_STATUS(status, s->write_h1_status); + + ZERO_STRUCT(rd); + rd.in.file.handle = h1; + rd.in.offset = ARRAY_SIZE(buf)/2; + rd.in.length = ARRAY_SIZE(buf)/2; + + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, s->read_h1_status); + + ZERO_STRUCT(wr); + wr.in.file.handle = h2; + wr.in.offset = ARRAY_SIZE(buf)/2; + wr.in.data = data_blob_const(buf, ARRAY_SIZE(buf)/2); + + status = smb2_write(tree, &wr); + CHECK_STATUS(status, s->write_h2_status); + + ZERO_STRUCT(rd); + rd.in.file.handle = h2; + rd.in.offset = ARRAY_SIZE(buf)/2; + rd.in.length = ARRAY_SIZE(buf)/2; + + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, s->read_h2_status); + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h1; + el[0].offset = ARRAY_SIZE(buf)/2; + el[0].length = ARRAY_SIZE(buf)/2; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + ZERO_STRUCT(wr); + wr.in.file.handle = h2; + wr.in.offset = ARRAY_SIZE(buf)/2; + wr.in.data = data_blob_const(buf, ARRAY_SIZE(buf)/2); + + status = smb2_write(tree, &wr); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h2; + rd.in.offset = ARRAY_SIZE(buf)/2; + rd.in.length = ARRAY_SIZE(buf)/2; + + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + return ret; +} + +static bool test_lock_rw_none(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct test_lock_read_write_state s = { + .fname = "lock_rw_none.dat", + .lock_flags = SMB2_LOCK_FLAG_NONE, + .write_h1_status = NT_STATUS_FILE_LOCK_CONFLICT, + .read_h1_status = NT_STATUS_OK, + .write_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, + .read_h2_status = NT_STATUS_OK, + }; + + if (!TARGET_IS_W2K8(torture)) { + torture_skip(torture, "RW-NONE tests the behavior of a " + "NONE-type lock, which is the same as a SHARED " + "lock but is granted due to a bug in W2K8. If " + "target is not W2K8 we skip this test.\n"); + } + + return test_lock_read_write(torture, tree, &s); +} + +static bool test_lock_rw_shared(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct test_lock_read_write_state s = { + .fname = "lock_rw_shared.dat", + .lock_flags = SMB2_LOCK_FLAG_SHARED, + .write_h1_status = NT_STATUS_FILE_LOCK_CONFLICT, + .read_h1_status = NT_STATUS_OK, + .write_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, + .read_h2_status = NT_STATUS_OK, + }; + + return test_lock_read_write(torture, tree, &s); +} + +static bool test_lock_rw_exclusive(struct torture_context *torture, + struct smb2_tree *tree) +{ + struct test_lock_read_write_state s = { + .fname = "lock_rw_exclusive.dat", + .lock_flags = SMB2_LOCK_FLAG_EXCLUSIVE, + .write_h1_status = NT_STATUS_OK, + .read_h1_status = NT_STATUS_OK, + .write_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, + .read_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, + }; + + return test_lock_read_write(torture, tree, &s); +} + +static bool test_lock_auto_unlock(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + ZERO_STRUCT(buf); + + status = torture_smb2_testfile(tree, "autounlock.txt", &h); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el[0]); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has \"pretty please\" bug. " + "A contending lock request on the same handle " + "unlocks the lock.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + } + + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + +done: + return ret; +} + +/* + test different lock ranges and see if different handles conflict +*/ +static bool test_lock(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + + const char *fname = BASEDIR "\\async.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = el; + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + torture_comment(torture, "Trying 0/0 lock\n"); + el[0].offset = 0x0000000000000000; + el[0].length = 0x0000000000000000; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Trying 0/1 lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = 0x0000000000000000; + el[0].length = 0x0000000000000001; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Trying 0xEEFFFFF lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = 0xEEFFFFFF; + el[0].length = 4000; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Trying 0xEF00000 lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = 0xEF000000; + el[0].length = 4000; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Trying (2^63 - 1)/1\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = 1; + el[0].offset <<= 63; + el[0].offset--; + el[0].length = 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Trying 2^63/1\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = 1; + el[0].offset <<= 63; + el[0].length = 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Trying max/0 lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = ~0; + el[0].length = 0; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Trying max/1 lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = ~0; + el[0].length = 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Trying max/2 lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = ~0; + el[0].length = 2; + status = smb2_lock(tree, &lck); + if (TARGET_SUPPORTS_INVALID_LOCK_RANGE(torture)) { + CHECK_STATUS(status, NT_STATUS_INVALID_LOCK_RANGE); + } else { + CHECK_STATUS(status, NT_STATUS_OK); + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + + torture_comment(torture, "Trying wrong handle unlock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[0].offset = 10001; + el[0].length = 40002; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + lck.in.file.handle = h2; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + test SMB2 LOCK async operation +*/ +static bool test_async(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + struct smb2_request *req = NULL; + + const char *fname = BASEDIR "\\async.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = el; + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 100; + el[0].length = 50; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + torture_comment(torture, " Acquire first lock\n"); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Second lock should pend on first\n"); + lck.in.file.handle = h2; + req = smb2_lock_send(tree, &lck); + WAIT_FOR_ASYNC_RESPONSE(req); + + torture_comment(torture, " Unlock first lock\n"); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Second lock should now succeed\n"); + lck.in.file.handle = h2; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock_recv(req, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + test SMB2 LOCK Cancel operation +*/ +static bool test_cancel(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + struct smb2_request *req = NULL; + + const char *fname = BASEDIR "\\cancel.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = el; + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 100; + el[0].length = 10; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + torture_comment(torture, "Testing basic cancel\n"); + + torture_comment(torture, " Acquire first lock\n"); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Second lock should pend on first\n"); + lck.in.file.handle = h2; + req = smb2_lock_send(tree, &lck); + WAIT_FOR_ASYNC_RESPONSE(req); + + torture_comment(torture, " Cancel the second lock\n"); + smb2_cancel(req); + lck.in.file.handle = h2; + status = smb2_lock_recv(req, &lck); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + torture_comment(torture, " Unlock first lock\n"); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + + torture_comment(torture, "Testing cancel by unlock\n"); + + torture_comment(torture, " Acquire first lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Second lock should pend on first\n"); + lck.in.file.handle = h2; + req = smb2_lock_send(tree, &lck); + WAIT_FOR_ASYNC_RESPONSE(req); + + torture_comment(torture, " Attempt to unlock pending second lock\n"); + lck.in.file.handle = h2; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, " Now cancel the second lock\n"); + smb2_cancel(req); + lck.in.file.handle = h2; + status = smb2_lock_recv(req, &lck); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + torture_comment(torture, " Unlock first lock\n"); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + + torture_comment(torture, "Testing cancel by close\n"); + + torture_comment(torture, " Acquire first lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Second lock should pend on first\n"); + lck.in.file.handle = h2; + req = smb2_lock_send(tree, &lck); + WAIT_FOR_ASYNC_RESPONSE(req); + + torture_comment(torture, " Close the second lock handle\n"); + smb2_util_close(tree, h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Check pending lock reply\n"); + status = smb2_lock_recv(req, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, " Unlock first lock\n"); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + test SMB2 LOCK Cancel by tree disconnect +*/ +static bool test_cancel_tdis(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + struct smb2_request *req = NULL; + + const char *fname = BASEDIR "\\cancel_tdis.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = el; + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 100; + el[0].length = 10; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + torture_comment(torture, "Testing cancel by tree disconnect\n"); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Acquire first lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Second lock should pend on first\n"); + lck.in.file.handle = h2; + req = smb2_lock_send(tree, &lck); + WAIT_FOR_ASYNC_RESPONSE(req); + + torture_comment(torture, " Disconnect the tree\n"); + smb2_tdis(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Check pending lock reply\n"); + status = smb2_lock_recv(req, &lck); + if (!NT_STATUS_EQUAL(status, NT_STATUS_RANGE_NOT_LOCKED)) { + /* + * The status depends on the server internals + * the order in which the files are closed + * by smb2_tdis(). + */ + CHECK_STATUS(status, NT_STATUS_OK); + } + + torture_comment(torture, " Attempt to unlock first lock\n"); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + /* + * Most Windows versions have a strange order to + * verify the session id, tree id and file id. + * (They should be checked in that order, but windows + * seems to check the file id before the others). + */ + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED)) { + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + } + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + test SMB2 LOCK Cancel by user logoff +*/ +static bool test_cancel_logoff(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + struct smb2_request *req = NULL; + + const char *fname = BASEDIR "\\cancel_logoff.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = el; + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 100; + el[0].length = 10; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + + torture_comment(torture, "Testing cancel by ulogoff\n"); + + torture_comment(torture, " Acquire first lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " Second lock should pend on first\n"); + lck.in.file.handle = h2; + req = smb2_lock_send(tree, &lck); + WAIT_FOR_ASYNC_RESPONSE(req); + + torture_comment(torture, " Logoff user\n"); + smb2_logoff(tree->session); + + torture_comment(torture, " Check pending lock reply\n"); + status = smb2_lock_recv(req, &lck); + if (!NT_STATUS_EQUAL(status, NT_STATUS_RANGE_NOT_LOCKED)) { + /* + * The status depends on the server internals + * the order in which the files are closed + * by smb2_logoff(). + */ + CHECK_STATUS(status, NT_STATUS_OK); + } + + torture_comment(torture, " Attempt to unlock first lock\n"); + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + /* + * Most Windows versions have a strange order to + * verify the session id, tree id and file id. + * (They should be checked in that order, but windows + * seems to check the file id before the others). + */ + if (!NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) { + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + } + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + * Test NT_STATUS_LOCK_NOT_GRANTED vs. NT_STATUS_FILE_LOCK_CONFLICT + * + * The SMBv1 protocol returns a different error code on lock acquisition + * failure depending on a number of parameters, including what error code + * was returned to the previous failure. + * + * SMBv2 has cleaned up these semantics and should always return + * NT_STATUS_LOCK_NOT_GRANTED to failed lock requests, and + * NT_STATUS_FILE_LOCK_CONFLICT to failed read/write requests due to a lock + * being held on that range. +*/ +static bool test_errorcode(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + + const char *fname = BASEDIR "\\errorcode.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = el; + + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 100; + el[0].length = 10; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + torture_comment(torture, "Testing LOCK_NOT_GRANTED vs. " + "FILE_LOCK_CONFLICT\n"); + + if (TARGET_IS_W2K8(torture)) { + torture_result(torture, TORTURE_SKIP, + "Target has \"pretty please\" bug. A contending lock " + "request on the same handle unlocks the lock."); + goto done; + } + + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Demonstrate that the first conflicting lock on each handle gives + * LOCK_NOT_GRANTED. */ + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* Demonstrate that each following conflict also gives + * LOCK_NOT_GRANTED */ + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* Demonstrate that the smbpid doesn't matter */ + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* Demonstrate that a 0-byte lock inside the locked range still + * gives the same error. */ + + el[0].offset = 102; + el[0].length = 0; + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* Demonstrate that a lock inside the locked range still gives the + * same error. */ + + el[0].offset = 102; + el[0].length = 5; + lck.in.file.handle = h; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.file.handle = h2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/** + * Tests zero byte locks. + */ + +struct double_lock_test { + struct smb2_lock_element lock1; + struct smb2_lock_element lock2; + NTSTATUS status; +}; + +static struct double_lock_test zero_byte_tests[] = { + /* {offset, count, reserved, flags}, + * {offset, count, reserved, flags}, + * status */ + + /** First, takes a zero byte lock at offset 10. Then: + * - Taking 0 byte lock at 10 should succeed. + * - Taking 1 byte locks at 9,10,11 should succeed. + * - Taking 2 byte lock at 9 should fail. + * - Taking 2 byte lock at 10 should succeed. + * - Taking 3 byte lock at 9 should fail. + */ + {{10, 0, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, + {{10, 0, 0, 0}, {9, 1, 0, 0}, NT_STATUS_OK}, + {{10, 0, 0, 0}, {10, 1, 0, 0}, NT_STATUS_OK}, + {{10, 0, 0, 0}, {11, 1, 0, 0}, NT_STATUS_OK}, + {{10, 0, 0, 0}, {9, 2, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, + {{10, 0, 0, 0}, {10, 2, 0, 0}, NT_STATUS_OK}, + {{10, 0, 0, 0}, {9, 3, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, + + /** Same, but opposite order. */ + {{10, 0, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, + {{9, 1, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, + {{10, 1, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, + {{11, 1, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, + {{9, 2, 0, 0}, {10, 0, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, + {{10, 2, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, + {{9, 3, 0, 0}, {10, 0, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, + + /** Zero zero case. */ + {{0, 0, 0, 0}, {0, 0, 0, 0}, NT_STATUS_OK}, +}; + +static bool test_zerobytelength(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + int i; + + const char *fname = BASEDIR "\\zero.txt"; + + torture_comment(torture, "Testing zero length byte range locks:\n"); + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Setup initial parameters */ + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + + /* Try every combination of locks in zero_byte_tests, using the same + * handle for both locks. The first lock is assumed to succeed. The + * second lock may contend, depending on the expected status. */ + for (i = 0; i < ARRAY_SIZE(zero_byte_tests); i++) { + torture_comment(torture, + " ... {%llu, %llu} + {%llu, %llu} = %s\n", + (unsigned long long) zero_byte_tests[i].lock1.offset, + (unsigned long long) zero_byte_tests[i].lock1.length, + (unsigned long long) zero_byte_tests[i].lock2.offset, + (unsigned long long) zero_byte_tests[i].lock2.length, + nt_errstr(zero_byte_tests[i].status)); + + /* Lock both locks. */ + lck.in.locks = &zero_byte_tests[i].lock1; + lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.locks = &zero_byte_tests[i].lock2; + lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CONT(status, zero_byte_tests[i].status); + + /* Unlock both locks in reverse order. */ + lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; + if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + + lck.in.locks = &zero_byte_tests[i].lock1; + lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + + /* Try every combination of locks in zero_byte_tests, using two + * different handles. */ + for (i = 0; i < ARRAY_SIZE(zero_byte_tests); i++) { + torture_comment(torture, + " ... {%llu, %llu} + {%llu, %llu} = %s\n", + (unsigned long long) zero_byte_tests[i].lock1.offset, + (unsigned long long) zero_byte_tests[i].lock1.length, + (unsigned long long) zero_byte_tests[i].lock2.offset, + (unsigned long long) zero_byte_tests[i].lock2.length, + nt_errstr(zero_byte_tests[i].status)); + + /* Lock both locks. */ + lck.in.file.handle = h; + lck.in.locks = &zero_byte_tests[i].lock1; + lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.file.handle = h2; + lck.in.locks = &zero_byte_tests[i].lock2; + lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CONT(status, zero_byte_tests[i].status); + + /* Unlock both locks in reverse order. */ + lck.in.file.handle = h2; + lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; + if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + + lck.in.file.handle = h; + lck.in.locks = &zero_byte_tests[i].lock1; + lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_zerobyteread(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + struct smb2_read rd; + + const char *fname = BASEDIR "\\zerobyteread.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Setup initial parameters */ + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + + ZERO_STRUCT(rd); + rd.in.file.handle = h2; + + torture_comment(torture, "Testing zero byte read on lock range:\n"); + + /* Take an exclusive lock */ + torture_comment(torture, " taking exclusive lock.\n"); + el[0].offset = 0; + el[0].length = 10; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + /* Try a zero byte read */ + torture_comment(torture, " reading 0 bytes.\n"); + rd.in.offset = 5; + rd.in.length = 0; + status = smb2_read(tree, tree, &rd); + torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Unlock lock */ + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + torture_comment(torture, "Testing zero byte read on zero byte lock " + "range:\n"); + + /* Take an exclusive lock */ + torture_comment(torture, " taking exclusive 0-byte lock.\n"); + el[0].offset = 5; + el[0].length = 0; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + + /* Try a zero byte read before the lock */ + torture_comment(torture, " reading 0 bytes before the lock.\n"); + rd.in.offset = 4; + rd.in.length = 0; + status = smb2_read(tree, tree, &rd); + torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try a zero byte read on the lock */ + torture_comment(torture, " reading 0 bytes on the lock.\n"); + rd.in.offset = 5; + rd.in.length = 0; + status = smb2_read(tree, tree, &rd); + torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try a zero byte read after the lock */ + torture_comment(torture, " reading 0 bytes after the lock.\n"); + rd.in.offset = 6; + rd.in.length = 0; + status = smb2_read(tree, tree, &rd); + torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, + "zero byte read did not return 0 bytes"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Unlock lock */ + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(lck.out.reserved, 0); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_unlock(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el1[1]; + struct smb2_lock_element el2[1]; + + const char *fname = BASEDIR "\\unlock.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Setup initial parameters */ + lck.in.locks = el1; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + el1[0].offset = 0; + el1[0].length = 10; + el1[0].reserved = 0x00000000; + + /* Take exclusive lock, then unlock it with a shared-unlock call. */ + + torture_comment(torture, "Testing unlock exclusive with shared\n"); + + torture_comment(torture, " taking exclusive lock.\n"); + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " try to unlock the exclusive with a shared " + "unlock call.\n"); + el1[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + torture_comment(torture, " try shared lock on h2, to test the " + "unlock.\n"); + lck.in.file.handle = h2; + el1[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + torture_comment(torture, " unlock the exclusive lock\n"); + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Unlock a shared lock with an exclusive-unlock call. */ + + torture_comment(torture, "Testing unlock shared with exclusive\n"); + + torture_comment(torture, " taking shared lock.\n"); + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_SHARED; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, " try to unlock the shared with an exclusive " + "unlock call.\n"); + el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + torture_comment(torture, " try exclusive lock on h2, to test the " + "unlock.\n"); + lck.in.file.handle = h2; + el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + torture_comment(torture, " unlock the exclusive lock\n"); + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test unlocking of stacked 0-byte locks. SMB2 0-byte lock behavior + * should be the same as >0-byte behavior. Exclusive locks should be + * unlocked before shared. */ + + torture_comment(torture, "Test unlocking stacked 0-byte locks\n"); + + lck.in.locks = el1; + lck.in.file.handle = h; + el1[0].offset = 10; + el1[0].length = 0; + el1[0].reserved = 0x00000000; + el2[0].offset = 5; + el2[0].length = 10; + el2[0].reserved = 0x00000000; + + /* lock 0-byte exclusive */ + el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* lock 0-byte shared */ + el1[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* test contention */ + lck.in.locks = el2; + lck.in.file.handle = h2; + el2[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.locks = el2; + lck.in.file.handle = h2; + el2[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* unlock */ + lck.in.locks = el1; + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* test - can we take a shared lock? */ + lck.in.locks = el2; + lck.in.file.handle = h2; + el2[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el2[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + lck.in.locks = el1; + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test unlocking of stacked exclusive, shared locks. Exclusive + * should be unlocked before any shared. */ + + torture_comment(torture, "Test unlocking stacked exclusive/shared " + "locks\n"); + + lck.in.locks = el1; + lck.in.file.handle = h; + el1[0].offset = 10; + el1[0].length = 10; + el1[0].reserved = 0x00000000; + el2[0].offset = 5; + el2[0].length = 10; + el2[0].reserved = 0x00000000; + + /* lock exclusive */ + el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* lock shared */ + el1[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* test contention */ + lck.in.locks = el2; + lck.in.file.handle = h2; + el2[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + lck.in.locks = el2; + lck.in.file.handle = h2; + el2[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* unlock */ + lck.in.locks = el1; + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* test - can we take a shared lock? */ + lck.in.locks = el2; + lck.in.file.handle = h2; + el2[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el2[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + lck.in.locks = el1; + lck.in.file.handle = h; + el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_multiple_unlock(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + + const char *fname = BASEDIR "\\unlock_multiple.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing multiple unlocks:\n"); + + /* Setup initial parameters */ + lck.in.lock_count = 0x0002; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 10; + el[0].reserved = 0x00000000; + el[1].offset = 10; + el[1].length = 10; + el[1].reserved = 0x00000000; + + /* Test1: Acquire second lock, but not first. */ + torture_comment(torture, " unlock 2 locks, first one not locked. " + "Expect no locks unlocked. \n"); + + lck.in.lock_count = 0x0001; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[1]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try to unlock both locks */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* Second lock should not be unlocked. */ + lck.in.lock_count = 0x0001; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[1]; + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has \"pretty please\" bug. " + "A contending lock request on the same handle " + "unlocks the lock.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + } + + /* cleanup */ + lck.in.lock_count = 0x0001; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = &el[1]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test2: Acquire first lock, but not second. */ + torture_comment(torture, " unlock 2 locks, second one not locked. " + "Expect first lock unlocked.\n"); + + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try to unlock both locks */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* First lock should be unlocked. */ + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test3: Request 2 locks, second will contend. What happens to the + * first? */ + torture_comment(torture, " request 2 locks, second one will contend. " + "Expect both to fail.\n"); + + /* Lock the second range */ + lck.in.lock_count = 0x0001; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[1]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Request both locks */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* First lock should be unlocked. */ + lck.in.lock_count = 0x0001; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + if (TARGET_IS_W2K8(torture)) { + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has \"pretty please\" bug. " + "A contending lock request on the same handle " + "unlocks the lock.\n"); + } else { + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + + /* Test4: Request unlock and lock. The lock contends, is the unlock + * then relocked? SMB2 doesn't like the lock and unlock requests in the + * same packet. The unlock will succeed, but the lock will return + * INVALID_PARAMETER. This behavior is described in MS-SMB2 + * 3.3.5.14.1 */ + torture_comment(torture, " request unlock and lock, second one will " + "error. Expect the unlock to succeed.\n"); + + /* Lock both ranges */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Attempt to unlock the first range and lock the second. The lock + * request will error. */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* The first lock should've been unlocked */ + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test10: SMB2 only test. Request unlock and lock in same packet. + * Neither contend. SMB2 doesn't like lock and unlock requests in the + * same packet. The unlock will succeed, but the lock will return + * INVALID_PARAMETER. */ + torture_comment(torture, " request unlock and lock. Unlock will " + "succeed, but lock will fail.\n"); + + /* Lock first range */ + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Attempt to unlock the first range and lock the second */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* Neither lock should still be locked */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Test11: SMB2 only test. Request lock and unlock in same packet. + * Neither contend. SMB2 doesn't like lock and unlock requests in the + * same packet. The lock will succeed, the unlock will fail with + * INVALID_PARAMETER, and the lock will be unlocked before return. */ + torture_comment(torture, " request lock and unlock. Both will " + "fail.\n"); + + /* Lock second range */ + lck.in.lock_count = 0x0001; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[1]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Attempt to lock the first range and unlock the second */ + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + /* First range should be unlocked, second locked. */ + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.lock_count = 0x0001; + el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = &el[1]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* cleanup */ + if (TARGET_IS_W2K8(torture)) { + lck.in.lock_count = 0x0001; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = &el[0]; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + torture_warning(torture, "Target has \"pretty please\" bug. " + "A contending lock request on the same handle " + "unlocks the lock.\n"); + } else { + lck.in.lock_count = 0x0002; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + el[1].flags = SMB2_LOCK_FLAG_UNLOCK; + lck.in.locks = el; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + } + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/** + * Test lock stacking + * - some tests ported from BASE-LOCK-LOCK5 + */ +static bool test_stacking(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + const char *fname = BASEDIR "\\stacking.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing lock stacking:\n"); + + /* Setup initial parameters */ + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 10; + el[0].reserved = 0x00000000; + + /* Try to take a shared lock, then a shared lock on same handle */ + torture_comment(torture, " stacking a shared on top of a shared" + "lock succeeds.\n"); + + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* cleanup */ + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + + /* Try to take an exclusive lock, then a shared lock on same handle */ + torture_comment(torture, " stacking a shared on top of an exclusive " + "lock succeeds.\n"); + + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* stacking a shared from a different handle should fail */ + lck.in.file.handle = h2; + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* cleanup */ + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* ensure the 4th unlock fails */ + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* ensure a second handle can now take an exclusive lock */ + lck.in.file.handle = h2; + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try to take an exclusive lock, then a shared lock on a + * different handle */ + torture_comment(torture, " stacking a shared on top of an exclusive " + "lock with different handles fails.\n"); + + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.file.handle = h2; + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* cleanup */ + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Try to take a shared lock, then stack an exclusive with same + * handle. */ + torture_comment(torture, " stacking an exclusive on top of a shared " + "lock fails.\n"); + + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* cleanup */ + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + torture_warning(torture, "Target has \"pretty please\" bug. " + "A contending lock request on the same handle " + "unlocks the lock.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_OK); + } + + /* Prove that two exclusive locks do not stack on the same handle. */ + torture_comment(torture, " two exclusive locks do not stack.\n"); + + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* cleanup */ + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + if (TARGET_IS_W2K8(torture)) { + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + torture_warning(torture, "Target has \"pretty please\" bug. " + "A contending lock request on the same handle " + "unlocks the lock.\n"); + } else { + CHECK_STATUS(status, NT_STATUS_OK); + } + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/** + * Test lock contention + * - shared lock should contend with exclusive lock on different handle + */ +static bool test_contend(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + const char *fname = BASEDIR "\\contend.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing lock contention:\n"); + + /* Setup initial parameters */ + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 10; + el[0].reserved = 0x00000000; + + /* Take an exclusive lock, then a shared lock on different handle */ + torture_comment(torture, " shared should contend on exclusive on " + "different handle.\n"); + + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.file.handle = h2; + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* cleanup */ + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/** + * Test locker context + * - test that pid does not affect the locker context + */ +static bool test_context(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + + const char *fname = BASEDIR "\\context.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing locker context:\n"); + + /* Setup initial parameters */ + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 10; + el[0].reserved = 0x00000000; + + /* Take an exclusive lock, then try to unlock with a different pid, + * same handle. This shows that the pid doesn't affect the locker + * context in SMB2. */ + torture_comment(torture, " pid shouldn't affect locker context\n"); + + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/** + * Test as much of the potential lock range as possible + * - test ported from BASE-LOCK-LOCK3 + */ +static bool test_range(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + uint64_t offset, i; + extern int torture_numops; + + const char *fname = BASEDIR "\\range.txt"; + +#define NEXT_OFFSET offset += (~(uint64_t)0) / torture_numops + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing locks spread across the 64-bit " + "offset range\n"); + + if (TARGET_IS_W2K8(torture)) { + torture_result(torture, TORTURE_SKIP, + "Target has \"pretty please\" bug. A contending lock " + "request on the same handle unlocks the lock."); + goto done; + } + + /* Setup initial parameters */ + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + torture_comment(torture, " establishing %d locks\n", torture_numops); + + for (offset=i=0; i<torture_numops; i++) { + NEXT_OFFSET; + + lck.in.file.handle = h; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "lock h failed at offset %#llx ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "lock h2 failed at offset %#llx ", + (unsigned long long) el[0].offset)); + } + + torture_comment(torture, " testing %d locks\n", torture_numops); + + for (offset=i=0; i<torture_numops; i++) { + NEXT_OFFSET; + + lck.in.file.handle = h; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h2 at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h2 at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + } + + torture_comment(torture, " removing %d locks\n", torture_numops); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + + for (offset=i=0; i<torture_numops; i++) { + NEXT_OFFSET; + + lck.in.file.handle = h; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "unlock from h failed at offset %#llx ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "unlock from h2 failed at offset %#llx ", + (unsigned long long) el[0].offset)); + } + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +static NTSTATUS test_smb2_lock(struct smb2_tree *tree, struct smb2_handle h, + uint64_t offset, uint64_t length, bool exclusive) +{ + struct smb2_lock lck; + struct smb2_lock_element el[1]; + NTSTATUS status; + + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = offset; + el[0].length = length; + el[0].reserved = 0x00000000; + el[0].flags = (exclusive ? + SMB2_LOCK_FLAG_EXCLUSIVE : + SMB2_LOCK_FLAG_SHARED) | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + status = smb2_lock(tree, &lck); + + return status; +} + +static NTSTATUS test_smb2_unlock(struct smb2_tree *tree, struct smb2_handle h, + uint64_t offset, uint64_t length) +{ + struct smb2_lock lck; + struct smb2_lock_element el[1]; + NTSTATUS status; + + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = offset; + el[0].length = length; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + + status = smb2_lock(tree, &lck); + + return status; +} + +#define EXPECTED(ret, v) if ((ret) != (v)) { \ + torture_result(torture, TORTURE_FAIL, __location__": subtest failed");\ + torture_comment(torture, "** "); correct = false; \ + } + +/** + * Test overlapping lock ranges from various lockers + * - some tests ported from BASE-LOCK-LOCK4 + */ +static bool test_overlap(struct torture_context *torture, + struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + uint8_t buf[200]; + bool correct = true; + + const char *fname = BASEDIR "\\overlap.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + + status = torture_smb2_testfile(tree2, fname, &h3); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing overlapping locks:\n"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 0, 4, true)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 2, 4, true)); + EXPECTED(ret, false); + torture_comment(torture, "the same session/handle %s set overlapping " + "exclusive locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 10, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 12, 4, false)); + EXPECTED(ret, true); + torture_comment(torture, "the same session/handle %s set overlapping " + "shared locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 20, 4, true)) && + NT_STATUS_IS_OK(test_smb2_lock(tree2, h3, 22, 4, true)); + EXPECTED(ret, false); + torture_comment(torture, "a different session %s set overlapping " + "exclusive locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 30, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree2, h3, 32, 4, false)); + EXPECTED(ret, true); + torture_comment(torture, "a different session %s set overlapping " + "shared locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 40, 4, true)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h2, 42, 4, true)); + EXPECTED(ret, false); + torture_comment(torture, "a different handle %s set overlapping " + "exclusive locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 50, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h2, 52, 4, false)); + EXPECTED(ret, true); + torture_comment(torture, "a different handle %s set overlapping " + "shared locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 110, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 112, 4, false)) && + NT_STATUS_IS_OK(test_smb2_unlock(tree, h, 110, 6)); + EXPECTED(ret, false); + torture_comment(torture, "the same handle %s coalesce read locks\n", + ret?"can":"cannot"); + + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_smb2_testfile(tree, fname, &h2); + CHECK_STATUS(status, NT_STATUS_OK); + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 0, 8, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h2, 0, 1, false)) && + NT_STATUS_IS_OK(smb2_util_close(tree, h)) && + NT_STATUS_IS_OK(torture_smb2_testfile(tree, fname, &h)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 7, 1, true)); + EXPECTED(ret, true); + torture_comment(torture, "the server %s have the NT byte range lock " + "bug\n", !ret?"does":"doesn't"); + +done: + smb2_util_close(tree2, h3); + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return correct; +} + +/** + * Test truncation of locked file + * - some tests ported from BASE-LOCK-LOCK7 + */ +static bool test_truncate(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + uint8_t buf[200]; + struct smb2_lock lck; + struct smb2_lock_element el[1]; + struct smb2_create io; + + const char *fname = BASEDIR "\\truncate.txt"; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing truncation of locked file:\n"); + + /* Setup initial parameters */ + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 10; + el[0].reserved = 0x00000000; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = fname; + + /* Take an exclusive lock */ + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* On second handle open the file with OVERWRITE disposition */ + torture_comment(torture, " overwrite disposition is allowed on a " + "locked file.\n"); + + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + status = smb2_create(tree, tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + smb2_util_close(tree, h2); + + /* On second handle open the file with SUPERSEDE disposition */ + torture_comment(torture, " supersede disposition is allowed on a " + "locked file.\n"); + + io.in.create_disposition = NTCREATEX_DISP_SUPERSEDE; + status = smb2_create(tree, tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + smb2_util_close(tree, h2); + + /* cleanup */ + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/** + * Test lock replay detection + * + * This test checks the SMB 2.1.0 behaviour of lock sequence checking, + * which is only turned on for resilient handles. + * + * Make it clear that this test is supposed to pass against the legacy + * Windows servers which violate the specification: + * + * [MS-SMB2] 3.3.5.14 Receiving an SMB2 LOCK Request + * + * ... + * + * ... if Open.IsResilient or Open.IsDurable or Open.IsPersistent is + * TRUE or if Connection.Dialect belongs to the SMB 3.x dialect family + * and Connection.ServerCapabilities includes + * SMB2_GLOBAL_CAP_MULTI_CHANNEL bit, the server SHOULD<314> + * perform lock sequence verification ... + * + * ... + * + * <314> Section 3.3.5.14: Windows 7 and Windows Server 2008 R2 perform + * lock sequence verification only when Open.IsResilient is TRUE. + * Windows 8 through Windows 10 v1909 and Windows Server 2012 through + * Windows Server v1909 perform lock sequence verification only when + * Open.IsResilient or Open.IsPersistent is TRUE. + * + * Note <314> also applies to all versions (at least) up to Windows Server v2004. + * + * Hopefully this will be fixed in future Windows versions and they + * will avoid Note <314>. + */ +static bool test_replay_broken_windows(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h; + struct smb2_ioctl ioctl; + struct smb2_lock lck; + struct smb2_lock_element el; + uint8_t res_req[8]; + const char *fname = BASEDIR "\\replay_broken_windows.txt"; + struct smb2_transport *transport = tree->session->transport; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB2_10) { + torture_skip(torture, "SMB 2.1.0 Dialect family or above \ + required for Lock Replay tests\n"); + } + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + torture_comment(torture, "Testing Open File:\n"); + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Setup initial parameters + */ + el = (struct smb2_lock_element) { + .length = 100, + .offset = 100, + }; + lck = (struct smb2_lock) { + .in.locks = &el, + .in.lock_count = 0x0001, + .in.file.handle = h + }; + + torture_comment(torture, "Testing Lock Replay detection [ignored]:\n"); + lck.in.lock_sequence = 0x010 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + if (NT_STATUS_IS_OK(status)) { + lck.in.lock_sequence = 0x020 + 0x2; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + lck.in.lock_sequence = 0x010 + 0x1; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + if (smbXcli_conn_protocol(transport->conn) > PROTOCOL_SMB2_10) { + torture_skip_goto(torture, done, + "SMB3 Server implements LockSequence " + "for all handles\n"); + } + } + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + if (smbXcli_conn_protocol(transport->conn) > PROTOCOL_SMB2_10) { + torture_comment(torture, + "\nSMB3 Server implements LockSequence as SMB 2.1.0" + " LEGACY BROKEN Windows!!!\n\n"); + } + torture_comment(torture, + "Testing SMB 2.1.0 LockSequence for ResilientHandles\n"); + + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Testing Set Resiliency:\n"); + SIVAL(res_req, 0, 1000); /* timeout */ + SIVAL(res_req, 4, 0); /* reserved */ + ioctl = (struct smb2_ioctl) { + .level = RAW_IOCTL_SMB2, + .in.file.handle = h, + .in.function = FSCTL_LMR_REQ_RESILIENCY, + .in.max_output_response = 0, + .in.flags = SMB2_IOCTL_FLAG_IS_FSCTL, + .in.out.data = res_req, + .in.out.length = sizeof(res_req) + }; + status = smb2_ioctl(tree, torture, &ioctl); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Test with an invalid bucket number (only 1..64 are valid). + * With an invalid number, lock replay detection is not performed. + */ + torture_comment(torture, "Testing Lock (ignored) Replay detection " + "(Bucket No: 0 (invalid)) [ignored]:\n"); + lck.in.lock_sequence = 0x000 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 1):\n"); + + /* + * Obtain Exclusive Lock of length 100 bytes using Bucket Num 1 + * and Bucket Seq 1. + */ + lck.in.lock_sequence = 0x010 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Server detects Replay of Byte Range locks using the Lock Sequence + * Numbers. And ignores the requests completely. + */ + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* status: still locked */ + + /* + * Server will not grant same Byte Range using a different Bucket Seq + */ + lck.in.lock_sequence = 0x010 + 0x2; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 2):\n"); + + /* + * Server will not grant same Byte Range using a different Bucket Num + */ + lck.in.lock_sequence = 0x020 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* status: still locked */ + + /* test with invalid bucket when file is locked */ + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 65 (invalid)) [ignored]:\n"); + + lck.in.lock_sequence = 0x410 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* status: unlocked */ + + /* + * Lock again for the unlock replay test + */ + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 64):\n"); + + /* + * Server will not grant same Byte Range using a different Bucket Num + */ + lck.in.lock_sequence = 0x400 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* + * Test Unlock replay detection + */ + lck.in.lock_sequence = 0x400 + 0x2; + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); /* new seq num ==> unlocked */ + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); /* replay detected ==> ignored */ + + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); /* same seq num ==> ignored */ + CHECK_STATUS(status, NT_STATUS_OK); + + /* verify it's unlocked: */ + lck.in.lock_sequence = 0x400 + 0x3; + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* status: not locked */ + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/** + * Test lock replay detection + * + * This test check the SMB 3 behaviour of lock sequence checking, + * which should be implemented for all handles. + * + * Make it clear that this test is supposed to pass a + * server implementing the specification: + * + * [MS-SMB2] 3.3.5.14 Receiving an SMB2 LOCK Request + * + * ... + * + * ... if Open.IsResilient or Open.IsDurable or Open.IsPersistent is + * TRUE or if Connection.Dialect belongs to the SMB 3.x dialect family + * and Connection.ServerCapabilities includes + * SMB2_GLOBAL_CAP_MULTI_CHANNEL bit, the server SHOULD<314> + * perform lock sequence verification ... + * + * ... + * + * <314> Section 3.3.5.14: Windows 7 and Windows Server 2008 R2 perform + * lock sequence verification only when Open.IsResilient is TRUE. + * Windows 8 through Windows 10 v1909 and Windows Server 2012 through + * Windows Server v1909 perform lock sequence verification only when + * Open.IsResilient or Open.IsPersistent is TRUE. + * + * Note <314> also applies to all versions (at least) up to Windows Server v2004. + * + * Hopefully this will be fixed in future Windows versions and they + * will avoid Note <314>. + */ +static bool _test_replay_smb3_specification(struct torture_context *torture, + struct smb2_tree *tree, + const char *testname, + bool use_durable) +{ + NTSTATUS status; + bool ret = true; + struct smb2_create io; + struct smb2_handle h; + struct smb2_lock lck; + struct smb2_lock_element el; + char fname[256]; + struct smb2_transport *transport = tree->session->transport; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(torture, "SMB 3.0.0 Dialect family or above \ + required for Lock Replay tests\n"); + } + + snprintf(fname, sizeof(fname), "%s\\%s.dat", BASEDIR, testname); + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + torture_comment(torture, "%s: Testing Open File:\n", testname); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = use_durable; + + status = smb2_create(tree, torture, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_VALUE(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VALUE(io.out.durable_open, use_durable); + CHECK_VALUE(io.out.durable_open_v2, false); + CHECK_VALUE(io.out.persistent_open, false); + + /* + * Setup initial parameters + */ + el = (struct smb2_lock_element) { + .length = 100, + .offset = 100, + }; + lck = (struct smb2_lock) { + .in.locks = &el, + .in.lock_count = 0x0001, + .in.file.handle = h + }; + + torture_comment(torture, + "Testing SMB 3 LockSequence for all Handles\n"); + + /* + * Test with an invalid bucket number (only 1..64 are valid). + * With an invalid number, lock replay detection is not performed. + */ + torture_comment(torture, "Testing Lock (ignored) Replay detection " + "(Bucket No: 0 (invalid)) [ignored]:\n"); + lck.in.lock_sequence = 0x000 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 1):\n"); + + /* + * Obtain Exclusive Lock of length 100 bytes using Bucket Num 1 + * and Bucket Seq 1. + */ + lck.in.lock_sequence = 0x010 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Server detects Replay of Byte Range locks using the Lock Sequence + * Numbers. And ignores the requests completely. + */ + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* status: still locked */ + + /* + * Server will not grant same Byte Range using a different Bucket Seq + */ + lck.in.lock_sequence = 0x010 + 0x2; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 2):\n"); + + /* + * Server will not grant same Byte Range using a different Bucket Num + */ + lck.in.lock_sequence = 0x020 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* status: still locked */ + + /* test with invalid bucket when file is locked */ + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 65 (invalid)) [ignored]:\n"); + + lck.in.lock_sequence = 0x410 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* status: unlocked */ + + /* + * Lock again for the unlock replay test + */ + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing Lock Replay detection " + "(Bucket No: 64):\n"); + + /* + * Server will not grant same Byte Range using a different Bucket Num + */ + lck.in.lock_sequence = 0x400 + 0x1; + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + /* + * Test Unlock replay detection + */ + lck.in.lock_sequence = 0x400 + 0x2; + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); /* new seq num ==> unlocked */ + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); /* replay detected ==> ignored */ + + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); /* same seq num ==> ignored */ + CHECK_STATUS(status, NT_STATUS_OK); + + /* verify it's unlocked: */ + lck.in.lock_sequence = 0x400 + 0x3; + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); + + /* status: not locked */ + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_replay_smb3_specification_durable(struct torture_context *torture, + struct smb2_tree *tree) +{ + const char *testname = "replay_smb3_specification_durable"; + struct smb2_transport *transport = tree->session->transport; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB2_10) { + torture_skip(torture, "SMB 2.1.0 Dialect family or above \ + required for Lock Replay tests on durable handles\n"); + } + + return _test_replay_smb3_specification(torture, tree, testname, true); +} + +static bool test_replay_smb3_specification_multi(struct torture_context *torture, + struct smb2_tree *tree) +{ + const char *testname = "replay_smb3_specification_multi"; + struct smb2_transport *transport = tree->session->transport; + uint32_t server_capabilities; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(torture, "SMB 3.0.0 Dialect family or above \ + required for Lock Replay tests on without durable handles\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(torture, "MULTI_CHANNEL is \ + required for Lock Replay tests on without durable handles\n"); + } + + return _test_replay_smb3_specification(torture, tree, testname, false); +} + +/** + * Test lock interaction between smbd and ctdb with tombstone records. + * + * Re-locking an unlocked record could lead to a deadlock between + * smbd and ctdb. Make sure we don't regress. + * + * https://bugzilla.samba.org/show_bug.cgi?id=12005 + * https://bugzilla.samba.org/show_bug.cgi?id=10008 + */ +static bool test_deadlock(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + uint8_t buf[200]; + const char *fname = BASEDIR "\\deadlock.txt"; + + if (!lpcfg_clustering(torture->lp_ctx)) { + torture_skip(torture, "Test must be run on a ctdb cluster\n"); + return true; + } + + status = torture_smb2_testdir(tree, BASEDIR, &_h); + torture_assert_ntstatus_ok(torture, status, + "torture_smb2_testdir failed"); + smb2_util_close(tree, _h); + + status = torture_smb2_testfile(tree, fname, &_h); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "torture_smb2_testfile failed"); + h = &_h; + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, *h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_util_write failed"); + + status = test_smb2_lock(tree, *h, 0, 1, true); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "test_smb2_lock failed"); + + status = test_smb2_unlock(tree, *h, 0, 1); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "test_smb2_unlock failed"); + + status = test_smb2_lock(tree, *h, 0, 1, true); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "test_smb2_lock failed"); + + status = test_smb2_unlock(tree, *h, 0, 1); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "test_smb2_unlock failed"); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* basic testing of SMB2 locking +*/ +struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "lock"); + torture_suite_add_1smb2_test(suite, "valid-request", + test_valid_request); + torture_suite_add_1smb2_test(suite, "rw-none", test_lock_rw_none); + torture_suite_add_1smb2_test(suite, "rw-shared", test_lock_rw_shared); + torture_suite_add_1smb2_test(suite, "rw-exclusive", + test_lock_rw_exclusive); + torture_suite_add_1smb2_test(suite, "auto-unlock", + test_lock_auto_unlock); + torture_suite_add_1smb2_test(suite, "lock", test_lock); + torture_suite_add_1smb2_test(suite, "async", test_async); + torture_suite_add_1smb2_test(suite, "cancel", test_cancel); + torture_suite_add_1smb2_test(suite, "cancel-tdis", test_cancel_tdis); + torture_suite_add_1smb2_test(suite, "cancel-logoff", + test_cancel_logoff); + torture_suite_add_1smb2_test(suite, "errorcode", test_errorcode); + torture_suite_add_1smb2_test(suite, "zerobytelength", + test_zerobytelength); + torture_suite_add_1smb2_test(suite, "zerobyteread", + test_zerobyteread); + torture_suite_add_1smb2_test(suite, "unlock", test_unlock); + torture_suite_add_1smb2_test(suite, "multiple-unlock", + test_multiple_unlock); + torture_suite_add_1smb2_test(suite, "stacking", test_stacking); + torture_suite_add_1smb2_test(suite, "contend", test_contend); + torture_suite_add_1smb2_test(suite, "context", test_context); + torture_suite_add_1smb2_test(suite, "range", test_range); + torture_suite_add_2smb2_test(suite, "overlap", test_overlap); + torture_suite_add_1smb2_test(suite, "truncate", test_truncate); + torture_suite_add_1smb2_test(suite, "replay_broken_windows", + test_replay_broken_windows); + torture_suite_add_1smb2_test(suite, "replay_smb3_specification_durable", + test_replay_smb3_specification_durable); + torture_suite_add_1smb2_test(suite, "replay_smb3_specification_multi", + test_replay_smb3_specification_multi); + torture_suite_add_1smb2_test(suite, "ctdb-delrec-deadlock", test_deadlock); + + suite->description = talloc_strdup(suite, "SMB2-LOCK tests"); + + return suite; +} diff --git a/source4/torture/smb2/mangle.c b/source4/torture/smb2/mangle.c new file mode 100644 index 0000000..bf14064 --- /dev/null +++ b/source4/torture/smb2/mangle.c @@ -0,0 +1,247 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester - mangling test + Copyright (C) Andrew Tridgell 2002 + Copyright (C) David Mulder 2019 + + 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/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "system/dir.h" +#include <tdb.h> +#include "../lib/util/util_tdb.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" + +#undef strcasecmp + +static TDB_CONTEXT *tdb; + +#define NAME_LENGTH 20 + +static unsigned int total, collisions, failures; + +static bool test_one(struct torture_context *tctx, struct smb2_tree *tree, + const char *name) +{ + struct smb2_handle fnum; + const char *shortname; + const char *name2; + NTSTATUS status; + TDB_DATA data; + struct smb2_create io = {0}; + + total++; + + io.in.fname = name; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA | + SEC_FILE_EXECUTE; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + status = smb2_create(tree, tree, &io); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "open of %s failed (%s)\n", name, + nt_errstr(status)); + return false; + } + fnum = io.out.file.handle; + + status = smb2_util_close(tree, fnum); + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, "close of %s failed (%s)\n", name, + nt_errstr(status)); + return false; + } + + /* get the short name */ + status = smb2_qpathinfo_alt_name(tctx, tree, name, &shortname); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "query altname of %s failed (%s)\n", + name, nt_errstr(status)); + return false; + } + + name2 = talloc_asprintf(tctx, "mangle_test\\%s", shortname); + status = smb2_util_unlink(tree, name2); + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, "unlink of %s (%s) failed (%s)\n", + name2, name, nt_errstr(status)); + return false; + } + + /* recreate by short name */ + io = (struct smb2_create){0}; + io.in.fname = name2; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA | + SEC_FILE_EXECUTE; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + status = smb2_create(tree, tree, &io); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "open2 of %s failed (%s)\n", name2, + nt_errstr(status)); + return false; + } + fnum = io.out.file.handle; + + status = smb2_util_close(tree, fnum); + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, "close of %s failed (%s)\n", name, + nt_errstr(status)); + return false; + } + + /* and unlink by long name */ + status = smb2_util_unlink(tree, name); + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, "unlink2 of %s (%s) failed (%s)\n", + name, name2, nt_errstr(status)); + failures++; + smb2_util_unlink(tree, name2); + return true; + } + + /* see if the short name is already in the tdb */ + data = tdb_fetch_bystring(tdb, shortname); + if (data.dptr) { + /* maybe its a duplicate long name? */ + if (strcasecmp(name, (const char *)data.dptr) != 0) { + /* we have a collision */ + collisions++; + torture_comment(tctx, "Collision between %s and %s" + " -> %s (coll/tot: %u/%u)\n", + name, data.dptr, shortname, collisions, + total); + } + free(data.dptr); + } else { + TDB_DATA namedata; + /* store it for later */ + namedata.dptr = discard_const_p(uint8_t, name); + namedata.dsize = strlen(name)+1; + tdb_store_bystring(tdb, shortname, namedata, TDB_REPLACE); + } + + return true; +} + + +static char *gen_name(struct torture_context *tctx) +{ + const char *chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-$~..."; + unsigned int max_idx = strlen(chars); + unsigned int len; + int i; + char *p = NULL; + char *name = NULL; + + name = talloc_strdup(tctx, "mangle_test\\"); + if (!name) { + return NULL; + } + + len = 1 + random() % NAME_LENGTH; + + name = talloc_realloc(tctx, name, char, strlen(name) + len + 6); + if (!name) { + return NULL; + } + p = name + strlen(name); + + for (i=0;i<len;i++) { + p[i] = chars[random() % max_idx]; + } + + p[i] = 0; + + if (ISDOT(p) || ISDOTDOT(p)) { + p[0] = '_'; + } + + /* have a high probability of a common lead char */ + if (random() % 2 == 0) { + p[0] = 'A'; + } + + /* and a medium probability of a common lead string */ + if ((len > 5) && (random() % 10 == 0)) { + strlcpy(p, "ABCDE", 6); + } + + /* and a high probability of a good extension length */ + if (random() % 2 == 0) { + char *s = strrchr(p, '.'); + if (s) { + s[4] = 0; + } + } + + return name; +} + + +bool torture_smb2_mangle(struct torture_context *torture, + struct smb2_tree *tree) +{ + extern int torture_numops; + int i; + bool ok; + NTSTATUS status; + + /* we will use an internal tdb to store the names we have used */ + tdb = tdb_open(NULL, 100000, TDB_INTERNAL, 0, 0); + torture_assert(torture, tdb, "ERROR: Failed to open tdb\n"); + + ok = smb2_util_setup_dir(torture, tree, "mangle_test"); + torture_assert(torture, ok, "smb2_util_setup_dir failed\n"); + + for (i=0;i<torture_numops;i++) { + char *name; + + name = gen_name(torture); + torture_assert(torture, name, "Name allocation failed\n"); + + ok = test_one(torture, tree, name); + torture_assert(torture, ok, talloc_asprintf(torture, + "Mangle names failed with %s", name)); + if (total && total % 100 == 0) { + if (torture_setting_bool(torture, "progress", true)) { + torture_comment(torture, + "collisions %u/%u - %.2f%% (%u failures)\r", + collisions, total, (100.0*collisions) / total, failures); + } + } + } + + smb2_util_unlink(tree, "mangle_test\\*"); + status = smb2_util_rmdir(tree, "mangle_test"); + torture_assert_ntstatus_ok(torture, status, + "ERROR: Failed to remove directory\n"); + + torture_comment(torture, + "\nTotal collisions %u/%u - %.2f%% (%u failures)\n", + collisions, total, (100.0*collisions) / total, failures); + + return (failures == 0); +} diff --git a/source4/torture/smb2/max_allowed.c b/source4/torture/smb2/max_allowed.c new file mode 100644 index 0000000..e68b0ff --- /dev/null +++ b/source4/torture/smb2/max_allowed.c @@ -0,0 +1,192 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester - deny mode scanning functions + Copyright (C) Andrew Tridgell 2001 + Copyright (C) David Mulder 2019 + + 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/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/security/security.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" + +#define MAXIMUM_ALLOWED_FILE "torture_maximum_allowed" +bool torture_smb2_maximum_allowed(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct security_descriptor *sd = NULL, *sd_orig = NULL; + struct smb2_create io = {0}; + TALLOC_CTX *mem_ctx = NULL; + struct smb2_handle fnum = {{0}}; + int i; + bool ret = true; + NTSTATUS status; + union smb_fileinfo q; + const char *owner_sid = NULL; + bool has_restore_privilege, has_backup_privilege, has_system_security_privilege; + + mem_ctx = talloc_init("torture_maximum_allowed"); + torture_assert_goto(tctx, mem_ctx != NULL, ret, done, + "talloc allocation failed\n"); + + if (!torture_setting_bool(tctx, "sacl_support", true)) + torture_warning(tctx, "Skipping SACL related tests!\n"); + + sd = security_descriptor_dacl_create(mem_ctx, + 0, NULL, NULL, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ, + 0, NULL); + torture_assert_goto(tctx, sd != NULL, ret, done, + "security descriptor creation failed\n"); + + /* Blank slate */ + smb2_util_unlink(tree, MAXIMUM_ALLOWED_FILE); + + /* create initial file with restrictive SD */ + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.fname = MAXIMUM_ALLOWED_FILE; + io.in.sec_desc = sd; + + status = smb2_create(tree, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + talloc_asprintf(tctx, "Incorrect status %s - should be %s\n", + nt_errstr(status), nt_errstr(NT_STATUS_OK))); + fnum = io.out.file.handle; + + /* the correct answers for this test depends on whether the + user has restore privileges. To find that out we first need + to know our SID - get it from the owner_sid of the file we + just created */ + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = fnum; + q.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER; + status = smb2_getinfo_file(tree, tctx, &q); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + talloc_asprintf(tctx, "Incorrect status %s - should be %s\n", + nt_errstr(status), nt_errstr(NT_STATUS_OK))); + sd_orig = q.query_secdesc.out.sd; + + owner_sid = dom_sid_string(tctx, sd_orig->owner_sid); + + status = torture_smb2_check_privilege(tree, + owner_sid, + sec_privilege_name(SEC_PRIV_RESTORE)); + has_restore_privilege = NT_STATUS_IS_OK(status); + torture_comment(tctx, "Checked SEC_PRIV_RESTORE for %s - %s\n", + owner_sid, + has_restore_privilege?"Yes":"No"); + + status = torture_smb2_check_privilege(tree, + owner_sid, + sec_privilege_name(SEC_PRIV_BACKUP)); + has_backup_privilege = NT_STATUS_IS_OK(status); + torture_comment(tctx, "Checked SEC_PRIV_BACKUP for %s - %s\n", + owner_sid, + has_backup_privilege?"Yes":"No"); + + status = torture_smb2_check_privilege(tree, + owner_sid, + sec_privilege_name(SEC_PRIV_SECURITY)); + has_system_security_privilege = NT_STATUS_IS_OK(status); + torture_comment(tctx, "Checked SEC_PRIV_SECURITY for %s - %s\n", + owner_sid, + has_system_security_privilege?"Yes":"No"); + + smb2_util_close(tree, fnum); + + for (i = 0; i < 32; i++) { + uint32_t mask = SEC_FLAG_MAXIMUM_ALLOWED | (1u << i); + /* + * SEC_GENERIC_EXECUTE is a complete subset of + * SEC_GENERIC_READ when mapped to specific bits, + * so we need to include it in the basic OK mask. + */ + uint32_t ok_mask = SEC_RIGHTS_FILE_READ | SEC_GENERIC_READ | SEC_GENERIC_EXECUTE | + SEC_STD_DELETE | SEC_STD_WRITE_DAC; + + /* + * Now SEC_RIGHTS_PRIV_RESTORE and SEC_RIGHTS_PRIV_BACKUP + * don't include any generic bits (they're used directly + * in the fileserver where the generic bits have already + * been mapped into file specific bits) we need to add the + * generic bits to the ok_mask when we have these privileges. + */ + if (has_restore_privilege) { + ok_mask |= SEC_RIGHTS_PRIV_RESTORE|SEC_GENERIC_WRITE; + } + if (has_backup_privilege) { + ok_mask |= SEC_RIGHTS_PRIV_BACKUP|SEC_GENERIC_READ; + } + if (has_system_security_privilege) { + ok_mask |= SEC_FLAG_SYSTEM_SECURITY; + } + + /* Skip all SACL related tests. */ + if ((!torture_setting_bool(tctx, "sacl_support", true)) && + (mask & SEC_FLAG_SYSTEM_SECURITY)) + continue; + + io = (struct smb2_create){0}; + io.in.desired_access = mask; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.impersonation_level = + NTCREATEX_IMPERSONATION_ANONYMOUS; + io.in.fname = MAXIMUM_ALLOWED_FILE; + + status = smb2_create(tree, mem_ctx, &io); + if (mask & ok_mask || + mask == SEC_FLAG_MAXIMUM_ALLOWED) { + torture_assert_ntstatus_ok_goto(tctx, status, ret, + done, talloc_asprintf(tctx, + "Incorrect status %s - should be %s\n", + nt_errstr(status), nt_errstr(NT_STATUS_OK))); + } else { + if (mask & SEC_FLAG_SYSTEM_SECURITY) { + torture_assert_ntstatus_equal_goto(tctx, + status, NT_STATUS_PRIVILEGE_NOT_HELD, + ret, done, talloc_asprintf(tctx, + "Incorrect status %s - should be %s\n", + nt_errstr(status), + nt_errstr(NT_STATUS_PRIVILEGE_NOT_HELD))); + } else { + torture_assert_ntstatus_equal_goto(tctx, + status, NT_STATUS_ACCESS_DENIED, + ret, done, talloc_asprintf(tctx, + "Incorrect status %s - should be %s\n", + nt_errstr(status), + nt_errstr(NT_STATUS_ACCESS_DENIED))); + } + } + + fnum = io.out.file.handle; + + smb2_util_close(tree, fnum); + } + + done: + smb2_util_unlink(tree, MAXIMUM_ALLOWED_FILE); + talloc_free(mem_ctx); + return ret; +} diff --git a/source4/torture/smb2/maxfid.c b/source4/torture/smb2/maxfid.c new file mode 100644 index 0000000..5f0b9fe --- /dev/null +++ b/source4/torture/smb2/maxfid.c @@ -0,0 +1,149 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 maxfid test + + Copyright (C) Christof Schmitt 2016 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +bool torture_smb2_maxfid(struct torture_context *tctx) +{ + bool ret = true; + NTSTATUS status; + struct smb2_tree *tree = NULL; + const char *dname = "smb2_maxfid"; + size_t i, maxfid; + struct smb2_handle *handles, dir_handle = { }; + size_t max_handles; + + /* + * We limited this to 65520 as socket_wrapper has a limit of + * 65535 (0xfff0) open sockets. + * + * It could be increased by setting the following env variable: + * + * SOCKET_WRAPPER_MAX_SOCKETS=100000 + */ + max_handles = torture_setting_int(tctx, "maxopenfiles", 65520); + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + + handles = talloc_array(tctx, struct smb2_handle, max_handles); + if (handles == 0) { + torture_fail(tctx, "Could not allocate handles array.\n"); + return false; + } + + smb2_deltree(tree, dname); + + status = torture_smb2_testdir(tree, dname, &dir_handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed"); + smb2_util_close(tree, dir_handle); + + torture_comment(tctx, "Creating subdirectories\n"); + + for (i = 0; i < max_handles; i += 1000) { + char *name; + struct smb2_create create = { }; + struct smb2_close close = { }; + + name = talloc_asprintf(tctx, "%s\\%zu", dname, i / 1000); + torture_assert_goto(tctx, (name != NULL), ret, done, + "no memory for directory name\n"); + + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = name; + + status = smb2_create(tree, tctx, &create); + talloc_free(name); + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE directory failed\n"); + + close.in.file.handle = create.out.file.handle; + status = smb2_close(tree, &close); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE directory failed\n"); + } + + torture_comment(tctx, "Testing maximum number of open files\n"); + + for (i = 0; i < max_handles; i++) { + char *name; + struct smb2_create create = { }; + + name = talloc_asprintf(tctx, "%s\\%zu\\%zu", dname, i / 1000, i); + torture_assert_goto(tctx, (name != NULL), ret, done, + "no memory for file name\n"); + + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = 0; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = name; + + status = smb2_create(tree, tctx, &create); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "create of %s failed: %s\n", + name, nt_errstr(status)); + talloc_free(name); + break; + } + talloc_free(name); + + handles[i] = create.out.file.handle; + } + + maxfid = i; + if (maxfid == max_handles) { + torture_comment(tctx, "Reached test limit of %zu open files. " + "Adjust to higher test with " + "--option=torture:maxopenfiles=NNN\n", maxfid); + } + + torture_comment(tctx, "Cleanup open files\n"); + + for (i = 0; i < maxfid; i++) { + status = smb2_util_close(tree, handles[i]); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE failed\n"); + } + +done: + smb2_deltree(tree, dname); + talloc_free(handles); + + return ret; +} diff --git a/source4/torture/smb2/maxwrite.c b/source4/torture/smb2/maxwrite.c new file mode 100644 index 0000000..bc52ebc --- /dev/null +++ b/source4/torture/smb2/maxwrite.c @@ -0,0 +1,137 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 write operations + + Copyright (C) Andrew Tridgell 2006 + + 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/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +#define FNAME "testmaxwrite.dat" + +/* + test writing +*/ +static NTSTATUS torture_smb2_write(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_handle handle) +{ + struct smb2_write w; + struct smb2_read r; + NTSTATUS status; + int i, len; + int max = 80000000; + int min = 1; + + while (max > min) { + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + + + len = 1+(min+max)/2; + + ZERO_STRUCT(w); + w.in.file.handle = handle; + w.in.offset = 0; + w.in.data = data_blob_talloc(tmp_ctx, NULL, len); + + for (i=0;i<len;i++) { + w.in.data.data[i] = i % 256; + } + + torture_comment(tctx, "trying to write %d bytes (min=%d max=%d)\n", + len, min, max); + + status = smb2_write(tree, &w); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "write failed - %s\n", nt_errstr(status)); + max = len-1; + status = smb2_util_close(tree, handle); + if (!NT_STATUS_IS_OK(status)) { + /* vista bug */ + torture_comment(tctx, "coping with server disconnect\n"); + talloc_free(tree); + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "failed to reconnect\n"); + return NT_STATUS_NET_WRITE_FAULT; + } + } + status = torture_smb2_createfile(tctx, tree, FNAME, &handle); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "failed to create file handle\n"); + talloc_free(tmp_ctx); + return status; + } + continue; + } else { + min = len; + } + + + ZERO_STRUCT(r); + r.in.file.handle = handle; + r.in.length = len; + r.in.offset = 0; + + torture_comment(tctx, "reading %d bytes\n", len); + + status = smb2_read(tree, tmp_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "read failed - %s\n", nt_errstr(status)); + } else if (w.in.data.length != r.out.data.length || + memcmp(w.in.data.data, r.out.data.data, len) != 0) { + torture_comment(tctx, "read data mismatch\n"); + } + + talloc_free(tmp_ctx); + } + + torture_comment(tctx, "converged: len=%d\n", max); + smb2_util_close(tree, handle); + smb2_util_unlink(tree, FNAME); + + return NT_STATUS_OK; +} + + + +/* + basic testing of SMB2 connection calls +*/ +bool torture_smb2_maxwrite(struct torture_context *tctx) +{ + struct smb2_tree *tree; + struct smb2_handle h1; + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + + torture_assert_ntstatus_ok(tctx, + torture_smb2_createfile(tctx, tree, FNAME, &h1), + "failed to create file handle"); + + torture_assert_ntstatus_ok(tctx, + torture_smb2_write(tctx, tree, h1), + "Write failed"); + + return true; +} diff --git a/source4/torture/smb2/mkdir.c b/source4/torture/smb2/mkdir.c new file mode 100644 index 0000000..f797b5c --- /dev/null +++ b/source4/torture/smb2/mkdir.c @@ -0,0 +1,105 @@ +/* + Unix SMB/CIFS implementation. + RAW_MKDIR_* and RAW_RMDIR_* individual test suite + Copyright (C) Andrew Tridgell 2003 + Copyright (C) David Mulder 2020 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" +#include "libcli/smb_composite/smb_composite.h" + +#define BASEDIR "mkdirtest" + +/* + test mkdir ops +*/ +bool torture_smb2_mkdir(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct smb2_handle h = {{0}}; + const char *path = BASEDIR "\\mkdir.dir"; + NTSTATUS status; + bool ret = true; + + ret = smb2_util_setup_dir(tctx, tree, BASEDIR); + torture_assert(tctx, ret, "Failed to setup up test directory: " BASEDIR); + + /* + basic mkdir + */ + status = smb2_util_mkdir(tree, path); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Incorrect status"); + + torture_comment(tctx, "Testing mkdir collision\n"); + + /* 2nd create */ + status = smb2_util_mkdir(tree, path); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_OBJECT_NAME_COLLISION, + ret, done, "Incorrect status"); + + /* basic rmdir */ + status = smb2_util_rmdir(tree, path); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Incorrect status"); + + status = smb2_util_rmdir(tree, path); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, "Incorrect status"); + + torture_comment(tctx, "Testing mkdir collision with file\n"); + + /* name collision with a file */ + smb2_create_complex_file(tctx, tree, path, &h); + smb2_util_close(tree, h); + status = smb2_util_mkdir(tree, path); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_OBJECT_NAME_COLLISION, + ret, done, "Incorrect status"); + + torture_comment(tctx, "Testing rmdir with file\n"); + + /* delete a file with rmdir */ + status = smb2_util_rmdir(tree, path); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NOT_A_DIRECTORY, + ret, done, "Incorrect status"); + + smb2_util_unlink(tree, path); + + torture_comment(tctx, "Testing invalid dir\n"); + + /* create an invalid dir */ + status = smb2_util_mkdir(tree, "..\\..\\.."); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_OBJECT_PATH_SYNTAX_BAD, + ret, done, "Incorrect status"); + + torture_comment(tctx, "Testing t2mkdir bad path\n"); + status = smb2_util_mkdir(tree, BASEDIR "\\bad_path\\bad_path"); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_OBJECT_PATH_NOT_FOUND, + ret, done, "Incorrect status"); + +done: + smb2_deltree(tree, BASEDIR); + return ret; +} diff --git a/source4/torture/smb2/multichannel.c b/source4/torture/smb2/multichannel.c new file mode 100644 index 0000000..7c3a60c --- /dev/null +++ b/source4/torture/smb2/multichannel.c @@ -0,0 +1,2701 @@ +/* + * Unix SMB/CIFS implementation. + * + * test SMB2 multichannel operations + * + * Copyright (C) Guenther Deschner, 2016 + * + * 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/>. + */ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_ioctl.h" +#include "../libcli/smb/smbXcli_base.h" +#include "lib/cmdline/cmdline.h" +#include "libcli/security/security.h" +#include "libcli/resolve/resolve.h" +#include "lib/socket/socket.h" +#include "lib/param/param.h" +#include "lib/events/events.h" +#include "oplock_break_handler.h" +#include "lease_break_handler.h" +#include "torture/smb2/block.h" + +#define BASEDIR "multichanneltestdir" + +#define CHECK_STATUS(status, correct) \ + torture_assert_ntstatus_equal_goto(tctx, status, correct,\ + ret, done, "") + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s" \ + " got 0x%x - should be 0x%x\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + goto done; \ + } } while (0) + +#define CHECK_VAL_GREATER_THAN(v, gt_val) do { \ + if ((v) <= (gt_val)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s): wrong value for %s got 0x%x - " \ + "should be greater than 0x%x\n", \ + __location__, #v, (int)v, (int)gt_val); \ + ret = false; \ + goto done; \ + } } while (0) + +#define CHECK_CREATED(__io, __created, __attribute) \ + do { \ + CHECK_VAL((__io)->out.create_action, \ + NTCREATEX_ACTION_ ## __created); \ + CHECK_VAL((__io)->out.alloc_size, 0); \ + CHECK_VAL((__io)->out.size, 0); \ + CHECK_VAL((__io)->out.file_attr, (__attribute)); \ + CHECK_VAL((__io)->out.reserved2, 0); \ + } while (0) + +#define CHECK_PTR(ptr, correct) do { \ + if ((ptr) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s " \ + "got 0x%p - should be 0x%p\n", \ + __location__, #ptr, ptr, correct); \ + ret = false; \ + goto done; \ + } } while (0) + +#define CHECK_LEASE(__io, __state, __oplevel, __key, __flags) \ + do { \ + CHECK_VAL((__io)->out.lease_response.lease_version, 1); \ + if (__oplevel) { \ + CHECK_VAL((__io)->out.oplock_level, \ + SMB2_OPLOCK_LEVEL_LEASE); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[0],\ + (__key)); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[1],\ + ~(__key)); \ + CHECK_VAL((__io)->out.lease_response.lease_state,\ + smb2_util_lease_state(__state)); \ + } else { \ + CHECK_VAL((__io)->out.oplock_level,\ + SMB2_OPLOCK_LEVEL_NONE); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[0],\ + 0); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[1],\ + 0); \ + CHECK_VAL((__io)->out.lease_response.lease_state, 0); \ + } \ + \ + CHECK_VAL((__io)->out.lease_response.lease_flags, (__flags)); \ + CHECK_VAL((__io)->out.lease_response.lease_duration, 0); \ + CHECK_VAL((__io)->out.lease_response.lease_epoch, 0); \ + } while (0) + +#define CHECK_LEASE_V2(__io, __state, __oplevel, __key, __flags, __parent, __epoch) \ + do { \ + CHECK_VAL((__io)->out.lease_response_v2.lease_version, 2); \ + if (__oplevel) { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], (__key)); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_state, smb2_util_lease_state(__state)); \ + } else { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_state, 0); \ + } \ + \ + CHECK_VAL((__io)->out.lease_response_v2.lease_flags, __flags); \ + if (__flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET) { \ + CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[0], (__parent)); \ + CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[1], ~(__parent)); \ + } \ + CHECK_VAL((__io)->out.lease_response_v2.lease_duration, 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_epoch, (__epoch)); \ + } while(0) + +#define CHECK_LEASE_BREAK_V2(__lb, __key, __from, __to, __break_flags, __new_epoch) \ + do { \ + CHECK_VAL((__lb).current_lease.lease_key.data[0], (__key)); \ + CHECK_VAL((__lb).current_lease.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__lb).current_lease.lease_state, smb2_util_lease_state(__from)); \ + CHECK_VAL((__lb).new_epoch, (__new_epoch)); \ + CHECK_VAL((__lb).break_flags, (__break_flags)); \ + CHECK_VAL((__lb).new_lease_state, smb2_util_lease_state(__to)); \ + } while(0) + +static bool test_ioctl_network_interface_info(struct torture_context *tctx, + struct smb2_tree *tree, + struct fsctl_net_iface_info *info) +{ + union smb_ioctl ioctl; + struct smb2_handle fh; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, + "server doesn't support SMB2_CAP_MULTI_CHANNEL\n"); + } + + ZERO_STRUCT(ioctl); + + ioctl.smb2.level = RAW_IOCTL_SMB2; + + fh.data[0] = UINT64_MAX; + fh.data[1] = UINT64_MAX; + + ioctl.smb2.in.file.handle = fh; + ioctl.smb2.in.function = FSCTL_QUERY_NETWORK_INTERFACE_INFO; + /* Windows client sets this to 64KiB */ + ioctl.smb2.in.max_output_response = 0x10000; + ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + torture_assert_ntstatus_ok(tctx, + smb2_ioctl(tree, tctx, &ioctl.smb2), + "FSCTL_QUERY_NETWORK_INTERFACE_INFO failed"); + + torture_assert(tctx, + (ioctl.smb2.out.out.length != 0), + "no interface info returned???"); + + torture_assert_ndr_success(tctx, + ndr_pull_struct_blob(&ioctl.smb2.out.out, tctx, info, + (ndr_pull_flags_fn_t)ndr_pull_fsctl_net_iface_info), + "failed to ndr pull"); + + if (DEBUGLVL(1)) { + NDR_PRINT_DEBUG(fsctl_net_iface_info, info); + } + + return true; +} + +static bool test_multichannel_interface_info(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct fsctl_net_iface_info info; + + return test_ioctl_network_interface_info(tctx, tree, &info); +} + +static struct smb2_tree *test_multichannel_create_channel( + struct torture_context *tctx, + const char *host, + const char *share, + struct cli_credentials *credentials, + const struct smbcli_options *_transport_options, + struct smb2_tree *parent_tree + ) +{ + struct smbcli_options transport_options = *_transport_options; + NTSTATUS status; + struct smb2_transport *transport; + struct smb2_session *session; + bool ret = true; + struct smb2_tree *tree; + + if (parent_tree) { + transport_options.only_negprot = true; + } + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree, + tctx->ev, + &transport_options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport = tree->session->transport; + transport->oplock.handler = torture_oplock_ack_handler; + transport->oplock.private_data = tree; + transport->lease.handler = torture_lease_handler; + transport->lease.private_data = tree; + torture_comment(tctx, "established transport [%p]\n", transport); + + /* + * If parent tree is set, bind the session to the parent transport + */ + if (parent_tree) { + session = smb2_session_channel(transport, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + parent_tree, parent_tree->session); + torture_assert_goto(tctx, session != NULL, ret, done, + "smb2_session_channel failed"); + + tree->smbXcli = parent_tree->smbXcli; + tree->session = session; + status = smb2_session_setup_spnego(session, + credentials, + 0 /* previous_session_id */); + CHECK_STATUS(status, NT_STATUS_OK); + torture_comment(tctx, "bound new session to parent\n"); + } + /* + * We absolutely need to make sure to send something over this + * connection to register the oplock break handler with the smb client + * connection. If we do not send something (at least a keepalive), we + * will *NEVER* receive anything over this transport. + */ + smb2_keepalive(transport); + +done: + if (ret) { + return tree; + } else { + return NULL; + } +} + +bool test_multichannel_create_channel_array( + struct torture_context *tctx, + const char *host, + const char *share, + struct cli_credentials *credentials, + struct smbcli_options *transport_options, + uint8_t num_trees, + struct smb2_tree **trees) +{ + uint8_t i; + + transport_options->client_guid = GUID_random(); + + for (i = 0; i < num_trees; i++) { + struct smb2_tree *parent_tree = NULL; + struct smb2_tree *tree = NULL; + struct smb2_transport *transport = NULL; + uint16_t local_port = 0; + + if (i > 0) { + parent_tree = trees[0]; + } + + torture_comment(tctx, "Setting up connection %d\n", i); + tree = test_multichannel_create_channel(tctx, host, share, + credentials, transport_options, + parent_tree); + torture_assert(tctx, tree, "failed to created new channel"); + + trees[i] = tree; + transport = tree->session->transport; + local_port = torture_get_local_port_from_transport(transport); + torture_comment(tctx, "transport[%d] uses tcp port: %d\n", + i, local_port); + } + + return true; +} + +bool test_multichannel_create_channels( + struct torture_context *tctx, + const char *host, + const char *share, + struct cli_credentials *credentials, + struct smbcli_options *transport_options, + struct smb2_tree **tree2A, + struct smb2_tree **tree2B, + struct smb2_tree **tree2C + ) +{ + struct smb2_tree **trees = NULL; + size_t num_trees = 0; + bool ret; + + torture_assert(tctx, tree2A, "tree2A required!"); + num_trees += 1; + torture_assert(tctx, tree2B, "tree2B required!"); + num_trees += 1; + if (tree2C != NULL) { + num_trees += 1; + } + trees = talloc_zero_array(tctx, struct smb2_tree *, num_trees); + torture_assert(tctx, trees, "out of memory"); + + ret = test_multichannel_create_channel_array(tctx, host, share, credentials, + transport_options, + num_trees, trees); + if (!ret) { + return false; + } + + *tree2A = trees[0]; + *tree2B = trees[1]; + if (tree2C != NULL) { + *tree2C = trees[2]; + } + + return true; +} + +static void test_multichannel_free_channels(struct smb2_tree *tree2A, + struct smb2_tree *tree2B, + struct smb2_tree *tree2C) +{ + TALLOC_FREE(tree2A); + TALLOC_FREE(tree2B); + TALLOC_FREE(tree2C); +} + +static bool test_multichannel_initial_checks(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + struct smb2_transport *transport1 = tree1->session->transport; + uint32_t server_capabilities; + struct fsctl_net_iface_info info; + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip_goto(tctx, fail, + "SMB 3.X Dialect family required for " + "Multichannel tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip_goto(tctx, fail, + "Server does not support multichannel."); + } + + torture_assert(tctx, + test_ioctl_network_interface_info(tctx, tree1, &info), + "failed to retrieve network interface info"); + + return true; +fail: + return false; +} + +static void test_multichannel_init_smb_create(struct smb2_create *io) +{ + io->in.durable_open = false; + io->in.durable_open_v2 = true; + io->in.persistent_open = false; + io->in.create_guid = GUID_random(); + io->in.timeout = 0x493E0; /* 300000 */ + /* windows 2016 returns 300000 0x493E0 */ +} + +/* Timer handler function notifies the registering function that time is up */ +static void timeout_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + bool *timesup = (bool *)private_data; + *timesup = true; +} + +/* + * Oplock break - Test 1 + * Test to confirm that server sends oplock breaks as expected. + * open file1 in session 2A + * open file2 in session 2B + * open file1 in session 1 + * oplock break received + * open file1 in session 1 + * oplock break received + * Cleanup + */ +static bool test_multichannel_oplock_break_test1(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client1_file2 = {{0}}; + struct smb2_handle h_client1_file3 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_handle h_client2_file2 = {{0}}; + struct smb2_handle h_client2_file3 = {{0}}; + struct smb2_create io1, io2, io3; + bool ret = true; + const char *fname1 = BASEDIR "\\oplock_break_test1.dat"; + const char *fname2 = BASEDIR "\\oplock_break_test2.dat"; + const char *fname3 = BASEDIR "\\oplock_break_test3.dat"; + struct smb2_tree *tree2A = NULL; + struct smb2_tree *tree2B = NULL; + struct smb2_tree *tree2C = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options transport2_options; + struct smb2_session *session1 = tree1->session; + uint16_t local_port = 0; + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Oplock break retry: Test1\n"); + + torture_reset_break_info(tctx, &break_info); + + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io1, fname1, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + test_multichannel_init_smb_create(&io1); + + smb2_oplock_create_share(&io2, fname2, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + test_multichannel_init_smb_create(&io2); + + smb2_oplock_create_share(&io3, fname3, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + test_multichannel_init_smb_create(&io3); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channels(tctx, host, share, + credentials, + &transport2_options, + &tree2A, &tree2B, NULL); + torture_assert(tctx, ret, "Could not create channels.\n"); + + /* 2a opens file1 */ + torture_comment(tctx, "client2 opens fname1 via session 2A\n"); + status = smb2_create(tree2A, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + /* 2b opens file2 */ + torture_comment(tctx, "client2 opens fname2 via session 2B\n"); + status = smb2_create(tree2B, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + + /* 1 opens file1 - batchoplock break? */ + torture_comment(tctx, "client1 opens fname1 via session 1\n"); + io1.in.oplock_level = smb2_util_oplock_level("b"); + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + + torture_reset_break_info(tctx, &break_info); + + /* 1 opens file2 - batchoplock break? */ + torture_comment(tctx, "client1 opens fname2 via session 1\n"); + io2.in.oplock_level = smb2_util_oplock_level("b"); + status = smb2_create(tree1, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("s")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + + /* cleanup everything */ + torture_reset_break_info(tctx, &break_info); + + smb2_util_close(tree1, h_client1_file1); + smb2_util_close(tree1, h_client1_file2); + smb2_util_close(tree1, h_client1_file3); + smb2_util_close(tree2A, h_client2_file1); + smb2_util_close(tree2A, h_client2_file2); + smb2_util_close(tree2A, h_client2_file3); + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + CHECK_VAL(break_info.count, 0); + test_multichannel_free_channels(tree2A, tree2B, tree2C); + tree2A = tree2B = tree2C = NULL; +done: + tree1->session = session1; + + smb2_util_close(tree1, h_client1_file1); + smb2_util_close(tree1, h_client1_file2); + smb2_util_close(tree1, h_client1_file3); + if (tree2A != NULL) { + smb2_util_close(tree2A, h_client2_file1); + smb2_util_close(tree2A, h_client2_file2); + smb2_util_close(tree2A, h_client2_file3); + } + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + smb2_deltree(tree1, BASEDIR); + + test_multichannel_free_channels(tree2A, tree2B, tree2C); + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/* + * Oplock Break Test 2 + * Test to see if oplock break retries are sent by the server. + * Also checks to see if new channels can be created and used + * after an oplock break retry. + * open file1 in 2A + * open file2 in 2B + * open file1 in session 1 + * oplock break received + * block channel on which oplock break received + * open file2 in session 1 + * oplock break not received. Retry received. + * file opened + * write to file2 on 2B + * Break sent to session 1(which has file2 open) + * Break sent to session 2A(which has read oplock) + * close file1 in session 1 + * open file1 with session 1 + * unblock blocked channel + * disconnect blocked channel + * connect channel 2D + * open file3 in 2D + * open file3 in session 1 + * receive break + */ +static bool test_multichannel_oplock_break_test2(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client1_file2 = {{0}}; + struct smb2_handle h_client1_file3 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_handle h_client2_file2 = {{0}}; + struct smb2_handle h_client2_file3 = {{0}}; + struct smb2_create io1, io2, io3; + bool ret = true; + const char *fname1 = BASEDIR "\\oplock_break_test1.dat"; + const char *fname2 = BASEDIR "\\oplock_break_test2.dat"; + const char *fname3 = BASEDIR "\\oplock_break_test3.dat"; + struct smb2_tree *tree2A = NULL; + struct smb2_tree *tree2B = NULL; + struct smb2_tree *tree2C = NULL; + struct smb2_tree *tree2D = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2 = NULL; + struct smbcli_options transport2_options; + struct smb2_session *session1 = tree1->session; + uint16_t local_port = 0; + DATA_BLOB blob; + bool block_setup = false; + bool block_ok = false; + bool unblock_ok = false; + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Oplock break retry: Test2\n"); + + torture_reset_break_info(tctx, &break_info); + + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io1, fname1, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + test_multichannel_init_smb_create(&io1); + + smb2_oplock_create_share(&io2, fname2, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + test_multichannel_init_smb_create(&io2); + + smb2_oplock_create_share(&io3, fname3, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + test_multichannel_init_smb_create(&io3); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channels(tctx, host, share, + credentials, + &transport2_options, + &tree2A, &tree2B, &tree2C); + torture_assert(tctx, ret, "Could not create channels.\n"); + + torture_comment(tctx, "client2 opens fname1 via session 2A\n"); + io1.in.oplock_level = smb2_util_oplock_level("b"); + status = smb2_create(tree2A, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + + torture_comment(tctx, "client2 opens fname2 via session 2B\n"); + io2.in.oplock_level = smb2_util_oplock_level("b"); + status = smb2_create(tree2B, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + + torture_comment(tctx, "client1 opens fname1 via session 1\n"); + io1.in.oplock_level = smb2_util_oplock_level("b"); + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + + /* We use the transport over which this oplock break was received */ + transport2 = break_info.received_transport; + torture_reset_break_info(tctx, &break_info); + + block_setup = test_setup_blocked_transports(tctx); + torture_assert(tctx, block_setup, "test_setup_blocked_transports"); + + /* block channel */ + block_ok = test_block_smb2_transport(tctx, transport2); + + torture_comment(tctx, "client1 opens fname2 via session 1\n"); + io2.in.oplock_level = smb2_util_oplock_level("b"); + status = smb2_create(tree1, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("s")); + + /* + * Samba downgrades oplock to a level 2 oplock. + * Windows 2016 revokes oplock + */ + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + torture_reset_break_info(tctx, &break_info); + + torture_comment(tctx, "Trying write to file2 on tree2B\n"); + + blob = data_blob_string_const("Here I am"); + status = smb2_util_write(tree2B, + h_client2_file2, + blob.data, + 0, + blob.length); + torture_assert_ntstatus_ok(tctx, status, + "failed to write file2 via channel 2B"); + + /* + * Samba: Write triggers 2 oplock breaks + * for session 1 which has file2 open + * for session 2 which has type 2 oplock + * Windows 2016: Only one oplock break for session 1 + */ + torture_wait_for_oplock_break(tctx); + CHECK_VAL_GREATER_THAN(break_info.count, 0); + torture_reset_break_info(tctx, &break_info); + + torture_comment(tctx, "client1 closes fname2 via session 1\n"); + smb2_util_close(tree1, h_client1_file2); + + torture_comment(tctx, "client1 opens fname2 via session 1 again\n"); + io2.in.oplock_level = smb2_util_oplock_level("b"); + status = smb2_create(tree1, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file2 = io2.out.file.handle; + io2.out.alloc_size = 0; + io2.out.size = 0; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("s")); + + /* + * now add a fourth channel and repeat the test, we need to reestablish + * transport2 because the remote end has invalidated our connection + */ + torture_comment(tctx, "Connecting session 2D\n"); + tree2D = test_multichannel_create_channel(tctx, host, share, + credentials, &transport2_options, tree2B); + if (!tree2D) { + goto done; + } + + torture_reset_break_info(tctx, &break_info); + torture_comment(tctx, "client 2 opening fname3 over transport2D\n"); + status = smb2_create(tree2D, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file3 = io3.out.file.handle; + CHECK_CREATED(&io3, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io3.out.oplock_level, smb2_util_oplock_level("b")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + torture_comment(tctx, "client1 opens fname3 via session 1\n"); + status = smb2_create(tree1, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file3 = io3.out.file.handle; + CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io3.out.oplock_level, smb2_util_oplock_level("s")); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + +done: + if (block_ok && !unblock_ok) { + test_unblock_smb2_transport(tctx, transport2); + } + test_cleanup_blocked_transports(tctx); + + tree1->session = session1; + + smb2_util_close(tree1, h_client1_file1); + smb2_util_close(tree1, h_client1_file2); + smb2_util_close(tree1, h_client1_file3); + if (tree2B != NULL) { + smb2_util_close(tree2B, h_client2_file1); + smb2_util_close(tree2B, h_client2_file2); + smb2_util_close(tree2B, h_client2_file3); + } + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + smb2_deltree(tree1, BASEDIR); + + test_multichannel_free_channels(tree2A, tree2B, tree2C); + if (tree2D != NULL) { + TALLOC_FREE(tree2D); + } + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +struct test_multichannel_oplock_break_state; + +struct test_multichannel_oplock_break_channel { + struct test_multichannel_oplock_break_state *state; + size_t idx; + char name[64]; + struct smb2_tree *tree; + bool blocked; + struct timeval break_time; + double full_duration; + double relative_duration; + uint8_t level; + size_t break_num; +}; + +struct test_multichannel_oplock_break_state { + struct torture_context *tctx; + struct timeval open_req_time; + struct timeval open_rep_time; + size_t num_breaks; + struct timeval last_break_time; + struct test_multichannel_oplock_break_channel channels[32]; +}; + +static bool test_multichannel_oplock_break_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct test_multichannel_oplock_break_channel *c = + (struct test_multichannel_oplock_break_channel *)private_data; + struct test_multichannel_oplock_break_state *state = c->state; + + c->break_time = timeval_current(); + c->full_duration = timeval_elapsed2(&state->open_req_time, + &c->break_time); + c->relative_duration = timeval_elapsed2(&state->last_break_time, + &c->break_time); + state->last_break_time = c->break_time; + c->level = level; + c->break_num = ++state->num_breaks; + + torture_comment(state->tctx, "Got OPLOCK break %zu on %s after %f ( %f)\n", + c->break_num, c->name, + c->relative_duration, + c->full_duration); + + return torture_oplock_ack_handler(transport, handle, level, c->tree); +} + +static bool test_multichannel_oplock_break_test3_windows(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct test_multichannel_oplock_break_state state = { + .tctx = tctx, + }; + struct test_multichannel_oplock_break_channel *open2_channel = NULL; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_create io1; + struct smb2_create io2; + bool ret = true; + const char *fname1 = BASEDIR "\\oplock_break_test3w.dat"; + struct smb2_tree *trees2[32] = { NULL, }; + size_t i; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options transport2_options; + struct smb2_session *session1 = tree1->session; + uint16_t local_port = 0; + bool block_setup = false; + bool block_ok = false; + double open_duration; + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Oplock break retry: Test3 (Windows behavior)\n"); + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io2, fname1, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channel_array(tctx, host, share, credentials, + &transport2_options, + ARRAY_SIZE(trees2), trees2); + torture_assert(tctx, ret, "Could not create channels.\n"); + + for (i = 0; i < ARRAY_SIZE(trees2); i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + struct smb2_transport *t = trees2[i]->session->transport; + + c->state = &state; + c->idx = i+1; + c->tree = trees2[i]; + snprintf(c->name, sizeof(c->name), "trees2_%zu", c->idx); + + t->oplock.handler = test_multichannel_oplock_break_handler; + t->oplock.private_data = c; + } + + open2_channel = &state.channels[0]; + + /* 2a opens file1 */ + torture_comment(tctx, "client2 opens fname1 via %s\n", + open2_channel->name); + status = smb2_create(open2_channel->tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io2.out.file.handle; + CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io2.out.durable_open_v2, false); + CHECK_VAL(io2.out.timeout, io2.in.timeout); + CHECK_VAL(io2.out.durable_open, false); + CHECK_VAL(break_info.count, 0); + + block_setup = test_setup_blocked_transports(tctx); + torture_assert(tctx, block_setup, "test_setup_blocked_transports"); + + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + struct smb2_transport *t = c->tree->session->transport; + + torture_comment(tctx, "Blocking %s\n", c->name); + block_ok = _test_block_smb2_transport(tctx, t, c->name); + torture_assert_goto(tctx, block_ok, ret, done, "we could not block tcp transport"); + c->blocked = true; + } + + /* 1 opens file2 */ + torture_comment(tctx, + "Client opens fname1 with session 1 with all %zu blocked\n", + ARRAY_SIZE(trees2)); + smb2_oplock_create_share(&io1, fname1, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + CHECK_VAL(lease_break_info.count, 0); + state.open_req_time = timeval_current(); + state.last_break_time = state.open_req_time; + status = smb2_create(tree1, mem_ctx, &io1); + state.open_rep_time = timeval_current(); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s")); + + CHECK_VAL(break_info.count, 1); + + open_duration = timeval_elapsed2(&state.open_req_time, + &state.open_rep_time); + torture_comment(tctx, "open_duration: %f\n", open_duration); + CHECK_VAL_GREATER_THAN(open_duration, 35); + + if (break_info.count == 0) { + torture_comment(tctx, + "Did not receive expected oplock break!!\n"); + } else { + torture_comment(tctx, "Received %d oplock break(s)!!\n", + break_info.count); + } + + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + size_t expected_break_num = 0; + + /* + * Only the latest channel gets a break notification + */ + if (i == (ARRAY_SIZE(state.channels) - 1)) { + expected_break_num = 1; + } + + torture_comment(tctx, "Verify %s\n", c->name); + torture_assert_int_equal(tctx, c->break_num, expected_break_num, + "Got oplock break on wrong channel"); + if (expected_break_num != 0) { + CHECK_VAL(c->level, smb2_util_oplock_level("s")); + } + } + +done: + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + struct smb2_transport *t = NULL; + + if (!c->blocked) { + continue; + } + + t = c->tree->session->transport; + + torture_comment(tctx, "Unblocking %s\n", c->name); + _test_unblock_smb2_transport(tctx, t, c->name); + c->blocked = false; + } + if (block_setup) { + test_cleanup_blocked_transports(tctx); + } + + tree1->session = session1; + + smb2_util_close(tree1, h_client1_file1); + if (trees2[0] != NULL) { + smb2_util_close(trees2[0], h_client2_file1); + } + + if (h != NULL) { + smb2_util_close(tree1, *h); + } + + smb2_util_unlink(tree1, fname1); + smb2_deltree(tree1, BASEDIR); + + for (i = 0; i < ARRAY_SIZE(trees2); i++) { + if (trees2[i] == NULL) { + continue; + } + TALLOC_FREE(trees2[i]); + } + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_multichannel_oplock_break_test3_specification(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct test_multichannel_oplock_break_state state = { + .tctx = tctx, + }; + struct test_multichannel_oplock_break_channel *open2_channel = NULL; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_create io1; + struct smb2_create io2; + bool ret = true; + const char *fname1 = BASEDIR "\\oplock_break_test3s.dat"; + struct smb2_tree *trees2[32] = { NULL, }; + size_t i; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options transport2_options; + struct smb2_session *session1 = tree1->session; + uint16_t local_port = 0; + bool block_setup = false; + bool block_ok = false; + double open_duration; + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Oplock break retry: Test3 (Specification behavior)\n"); + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io2, fname1, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channel_array(tctx, host, share, credentials, + &transport2_options, + ARRAY_SIZE(trees2), trees2); + torture_assert(tctx, ret, "Could not create channels.\n"); + + for (i = 0; i < ARRAY_SIZE(trees2); i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + struct smb2_transport *t = trees2[i]->session->transport; + + c->state = &state; + c->idx = i+1; + c->tree = trees2[i]; + snprintf(c->name, sizeof(c->name), "trees2_%zu", c->idx); + + t->oplock.handler = test_multichannel_oplock_break_handler; + t->oplock.private_data = c; + } + + open2_channel = &state.channels[0]; + + /* 2a opens file1 */ + torture_comment(tctx, "client2 opens fname1 via %s\n", + open2_channel->name); + status = smb2_create(open2_channel->tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io2.out.file.handle; + CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io2.out.durable_open_v2, false); + CHECK_VAL(io2.out.timeout, io2.in.timeout); + CHECK_VAL(io2.out.durable_open, false); + CHECK_VAL(break_info.count, 0); + + block_setup = test_setup_blocked_transports(tctx); + torture_assert(tctx, block_setup, "test_setup_blocked_transports"); + + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + struct smb2_transport *t = c->tree->session->transport; + + torture_comment(tctx, "Blocking %s\n", c->name); + block_ok = _test_block_smb2_transport(tctx, t, c->name); + torture_assert_goto(tctx, block_ok, ret, done, "we could not block tcp transport"); + c->blocked = true; + } + + /* 1 opens file2 */ + torture_comment(tctx, + "Client opens fname1 with session 1 with all %zu blocked\n", + ARRAY_SIZE(trees2)); + smb2_oplock_create_share(&io1, fname1, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + CHECK_VAL(lease_break_info.count, 0); + state.open_req_time = timeval_current(); + state.last_break_time = state.open_req_time; + status = smb2_create(tree1, mem_ctx, &io1); + state.open_rep_time = timeval_current(); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + + CHECK_VAL_GREATER_THAN(break_info.count, 1); + + open_duration = timeval_elapsed2(&state.open_req_time, + &state.open_rep_time); + torture_comment(tctx, "open_duration: %f\n", open_duration); + if (break_info.count < ARRAY_SIZE(state.channels)) { + CHECK_VAL_GREATER_THAN(open_duration, 35); + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s")); + } else { + CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b")); + } + + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + if (break_info.count >= ARRAY_SIZE(state.channels)) { + break; + } + torture_comment(tctx, "Received %d oplock break(s) wait for more!!\n", + break_info.count); + torture_wait_for_oplock_break(tctx); + } + + if (break_info.count == 0) { + torture_comment(tctx, + "Did not receive expected oplock break!!\n"); + } else { + torture_comment(tctx, "Received %d oplock break(s)!!\n", + break_info.count); + } + + if (break_info.count < ARRAY_SIZE(state.channels)) { + CHECK_VAL_GREATER_THAN(break_info.count, 3); + } else { + CHECK_VAL(break_info.count, ARRAY_SIZE(state.channels)); + } + + for (i = 0; i < break_info.count; i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + + torture_comment(tctx, "Verify %s\n", c->name); + torture_assert_int_equal(tctx, c->break_num, c->idx, + "Got oplock break on wrong channel"); + CHECK_VAL(c->level, smb2_util_oplock_level("s")); + } + +done: + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + struct test_multichannel_oplock_break_channel *c = &state.channels[i]; + struct smb2_transport *t = NULL; + + if (!c->blocked) { + continue; + } + + t = c->tree->session->transport; + + torture_comment(tctx, "Unblocking %s\n", c->name); + _test_unblock_smb2_transport(tctx, t, c->name); + c->blocked = false; + } + if (block_setup) { + test_cleanup_blocked_transports(tctx); + } + + tree1->session = session1; + + smb2_util_close(tree1, h_client1_file1); + if (trees2[0] != NULL) { + smb2_util_close(trees2[0], h_client2_file1); + } + + if (h != NULL) { + smb2_util_close(tree1, *h); + } + + smb2_util_unlink(tree1, fname1); + smb2_deltree(tree1, BASEDIR); + + for (i = 0; i < ARRAY_SIZE(trees2); i++) { + if (trees2[i] == NULL) { + continue; + } + TALLOC_FREE(trees2[i]); + } + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +static const uint64_t LEASE1F1 = 0xBADC0FFEE0DDF00Dull; +static const uint64_t LEASE1F2 = 0xBADC0FFEE0DDD00Dull; +static const uint64_t LEASE1F3 = 0xDADC0FFEE0DDD00Dull; +static const uint64_t LEASE2F1 = 0xDEADBEEFFEEDBEADull; +static const uint64_t LEASE2F2 = 0xDAD0FFEDD00DF00Dull; +static const uint64_t LEASE2F3 = 0xBAD0FFEDD00DF00Dull; + +/* + * Lease Break Test 1: + * Test to check if lease breaks are sent by the server as expected. + * open file1 in session 2A + * open file2 in session 2B + * open file3 in session 2C + * open file1 in session 1 + * lease break sent + * open file2 in session 1 + * lease break sent + * open file3 in session 1 + * lease break sent + */ +static bool test_multichannel_lease_break_test1(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client1_file2 = {{0}}; + struct smb2_handle h_client1_file3 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_handle h_client2_file2 = {{0}}; + struct smb2_handle h_client2_file3 = {{0}}; + struct smb2_create io1, io2, io3; + bool ret = true; + const char *fname1 = BASEDIR "\\lease_break_test1.dat"; + const char *fname2 = BASEDIR "\\lease_break_test2.dat"; + const char *fname3 = BASEDIR "\\lease_break_test3.dat"; + struct smb2_tree *tree2A = NULL; + struct smb2_tree *tree2B = NULL; + struct smb2_tree *tree2C = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options transport2_options; + struct smb2_session *session1 = tree1->session; + uint16_t local_port = 0; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_lease ls3; + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Lease break retry: Test1\n"); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + CHECK_VAL(lease_break_info.count, 0); + + smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1, + smb2_util_lease_state("RHW")); + test_multichannel_init_smb_create(&io1); + + smb2_lease_create(&io2, &ls2, false, fname2, LEASE2F2, + smb2_util_lease_state("RHW")); + test_multichannel_init_smb_create(&io2); + + smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3, + smb2_util_lease_state("RHW")); + test_multichannel_init_smb_create(&io3); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channels(tctx, host, share, + credentials, + &transport2_options, + &tree2A, &tree2B, &tree2C); + torture_assert(tctx, ret, "Could not create channels.\n"); + + /* 2a opens file1 */ + torture_comment(tctx, "client2 opens fname1 via session 2A\n"); + status = smb2_create(tree2A, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RHW", true, LEASE2F1, 0); + CHECK_VAL(lease_break_info.count, 0); + + /* 2b opens file2 */ + torture_comment(tctx, "client2 opens fname2 via session 2B\n"); + status = smb2_create(tree2B, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "RHW", true, LEASE2F2, 0); + CHECK_VAL(lease_break_info.count, 0); + + /* 2c opens file3 */ + torture_comment(tctx, "client2 opens fname3 via session 2C\n"); + smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3, + smb2_util_lease_state("RHW")); + status = smb2_create(tree2C, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file3 = io3.out.file.handle; + CHECK_CREATED(&io3, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io3, "RHW", true, LEASE2F3, 0); + CHECK_VAL(lease_break_info.count, 0); + + /* 1 opens file1 - lease break? */ + torture_comment(tctx, "client1 opens fname1 via session 1\n"); + smb2_lease_create(&io1, &ls1, false, fname1, LEASE1F1, + smb2_util_lease_state("RHW")); + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RH", true, LEASE1F1, 0); + CHECK_BREAK_INFO("RHW", "RH", LEASE2F1); + CHECK_VAL(lease_break_info.count, 1); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* 1 opens file2 - lease break? */ + torture_comment(tctx, "client1 opens fname2 via session 1\n"); + smb2_lease_create(&io2, &ls2, false, fname2, LEASE1F2, + smb2_util_lease_state("RHW")); + status = smb2_create(tree1, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "RH", true, LEASE1F2, 0); + CHECK_BREAK_INFO("RHW", "RH", LEASE2F2); + CHECK_VAL(lease_break_info.count, 1); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* 1 opens file3 - lease break? */ + torture_comment(tctx, "client1 opens fname3 via session 1\n"); + smb2_lease_create(&io3, &ls3, false, fname3, LEASE1F3, + smb2_util_lease_state("RHW")); + status = smb2_create(tree1, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file3 = io3.out.file.handle; + CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io3, "RH", true, LEASE1F3, 0); + CHECK_BREAK_INFO("RHW", "RH", LEASE2F3); + CHECK_VAL(lease_break_info.count, 1); + + /* cleanup everything */ + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_util_close(tree1, h_client1_file1); + smb2_util_close(tree1, h_client1_file2); + smb2_util_close(tree1, h_client1_file3); + smb2_util_close(tree2A, h_client2_file1); + smb2_util_close(tree2A, h_client2_file2); + smb2_util_close(tree2A, h_client2_file3); + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + CHECK_VAL(lease_break_info.count, 0); + test_multichannel_free_channels(tree2A, tree2B, tree2C); + tree2A = tree2B = tree2C = NULL; +done: + tree1->session = session1; + + smb2_util_close(tree1, h_client1_file1); + smb2_util_close(tree1, h_client1_file2); + smb2_util_close(tree1, h_client1_file3); + if (tree2A != NULL) { + smb2_util_close(tree2A, h_client2_file1); + smb2_util_close(tree2A, h_client2_file2); + smb2_util_close(tree2A, h_client2_file3); + } + + if (h != NULL) { + smb2_util_close(tree1, *h); + } + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + smb2_deltree(tree1, BASEDIR); + + test_multichannel_free_channels(tree2A, tree2B, tree2C); + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/* + * Lease Break Test 2: + * Test for lease break retries being sent by the server. + * Connect 2A, 2B + * open file1 in session 2A + * open file2 in session 2B + * block 2A + * open file2 in session 1 + * lease break retry reaches the client? + * Connect 2C + * open file3 in session 2C + * unblock 2A + * open file1 in session 1 + * lease break reaches the client? + * open file3 in session 1 + * lease break reached the client? + * Cleanup + * On deletion by 1, lease breaks sent for file1, file2 and file3 + * on 2B + * This changes RH lease to R for Session 2. + * (This has been disabled while we add support for sending lease + * break for handle leases.) + */ +static bool test_multichannel_lease_break_test2(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client1_file2 = {{0}}; + struct smb2_handle h_client1_file3 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_handle h_client2_file2 = {{0}}; + struct smb2_handle h_client2_file3 = {{0}}; + struct smb2_create io1, io2, io3; + bool ret = true; + const char *fname1 = BASEDIR "\\lease_break_test1.dat"; + const char *fname2 = BASEDIR "\\lease_break_test2.dat"; + const char *fname3 = BASEDIR "\\lease_break_test3.dat"; + struct smb2_tree *tree2A = NULL; + struct smb2_tree *tree2B = NULL; + struct smb2_tree *tree2C = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2A = NULL; + struct smbcli_options transport2_options; + struct smb2_session *session1 = tree1->session; + uint16_t local_port = 0; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_lease ls3; + bool block_setup = false; + bool block_ok = false; + bool unblock_ok = false; + + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Lease break retry: Test2\n"); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + CHECK_VAL(lease_break_info.count, 0); + + smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1, + smb2_util_lease_state("RHW")); + test_multichannel_init_smb_create(&io1); + + smb2_lease_create(&io2, &ls2, false, fname2, LEASE2F2, + smb2_util_lease_state("RHW")); + test_multichannel_init_smb_create(&io2); + + smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3, + smb2_util_lease_state("RHW")); + test_multichannel_init_smb_create(&io3); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channels(tctx, host, share, + credentials, + &transport2_options, + &tree2A, &tree2B, NULL); + torture_assert(tctx, ret, "Could not create channels.\n"); + transport2A = tree2A->session->transport; + + /* 2a opens file1 */ + torture_comment(tctx, "client2 opens fname1 via session 2A\n"); + smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1, + smb2_util_lease_state("RHW")); + status = smb2_create(tree2A, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RHW", true, LEASE2F1, 0); + CHECK_VAL(io1.out.durable_open_v2, false); //true); + CHECK_VAL(io1.out.timeout, io1.in.timeout); + CHECK_VAL(io1.out.durable_open, false); + CHECK_VAL(lease_break_info.count, 0); + + /* 2b opens file2 */ + torture_comment(tctx, "client2 opens fname2 via session 2B\n"); + smb2_lease_create(&io2, &ls2, false, fname2, LEASE2F2, + smb2_util_lease_state("RHW")); + status = smb2_create(tree2B, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "RHW", true, LEASE2F2, 0); + CHECK_VAL(io2.out.durable_open_v2, false); //true); + CHECK_VAL(io2.out.timeout, io2.in.timeout); + CHECK_VAL(io2.out.durable_open, false); + CHECK_VAL(lease_break_info.count, 0); + + block_setup = test_setup_blocked_transports(tctx); + torture_assert(tctx, block_setup, "test_setup_blocked_transports"); + + torture_comment(tctx, "Blocking 2A\n"); + /* Block 2A */ + block_ok = test_block_smb2_transport(tctx, transport2A); + torture_assert(tctx, block_ok, "we could not block tcp transport"); + + torture_wait_for_lease_break(tctx); + CHECK_VAL(lease_break_info.count, 0); + + /* 1 opens file2 */ + torture_comment(tctx, + "Client opens fname2 with session1 with 2A blocked\n"); + smb2_lease_create(&io2, &ls2, false, fname2, LEASE1F2, + smb2_util_lease_state("RHW")); + status = smb2_create(tree1, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io2, "RH", true, LEASE1F2, 0); + CHECK_VAL(io2.out.durable_open_v2, false); + CHECK_VAL(io2.out.timeout, 0); + CHECK_VAL(io2.out.durable_open, false); + + if (lease_break_info.count == 0) { + torture_comment(tctx, + "Did not receive expected lease break!!\n"); + } else { + torture_comment(tctx, "Received %d lease break(s)!!\n", + lease_break_info.count); + } + + /* + * We got breaks on both channels + * (one failed on the blocked connection) + */ + CHECK_VAL(lease_break_info.count, 2); + lease_break_info.count -= 1; + CHECK_VAL(lease_break_info.failures, 1); + lease_break_info.failures -= 1; + CHECK_BREAK_INFO("RHW", "RH", LEASE2F2); + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* Connect 2C */ + torture_comment(tctx, "Connecting session 2C\n"); + talloc_free(tree2C); + tree2C = test_multichannel_create_channel(tctx, host, share, + credentials, &transport2_options, tree2A); + if (!tree2C) { + goto done; + } + + /* 2c opens file3 */ + torture_comment(tctx, "client2 opens fname3 via session 2C\n"); + smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3, + smb2_util_lease_state("RHW")); + status = smb2_create(tree2C, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file3 = io3.out.file.handle; + CHECK_CREATED(&io3, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io3, "RHW", true, LEASE2F3, 0); + CHECK_VAL(io3.out.durable_open_v2, false); + CHECK_VAL(io3.out.timeout, io2.in.timeout); + CHECK_VAL(io3.out.durable_open, false); + CHECK_VAL(lease_break_info.count, 0); + + /* Unblock 2A */ + torture_comment(tctx, "Unblocking 2A\n"); + unblock_ok = test_unblock_smb2_transport(tctx, transport2A); + torture_assert(tctx, unblock_ok, "we could not unblock tcp transport"); + + /* 1 opens file1 */ + torture_comment(tctx, "Client opens fname1 with session 1\n"); + smb2_lease_create(&io1, &ls1, false, fname1, LEASE1F1, + smb2_util_lease_state("RHW")); + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RH", true, LEASE1F1, 0); + + if (lease_break_info.count == 0) { + torture_comment(tctx, + "Did not receive expected lease break!!\n"); + } else { + torture_comment(tctx, + "Received %d lease break(s)!!\n", + lease_break_info.count); + } + CHECK_VAL(lease_break_info.count, 1); + CHECK_BREAK_INFO("RHW", "RH", LEASE2F1); + torture_reset_lease_break_info(tctx, &lease_break_info); + + /*1 opens file3 */ + torture_comment(tctx, "client opens fname3 via session 1\n"); + + smb2_lease_create(&io3, &ls3, false, fname3, LEASE1F3, + smb2_util_lease_state("RHW")); + status = smb2_create(tree1, mem_ctx, &io3); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file3 = io3.out.file.handle; + CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io3, "RH", true, LEASE1F3, 0); + + if (lease_break_info.count == 0) { + torture_comment(tctx, + "Did not receive expected lease break!!\n"); + } else { + torture_comment(tctx, + "Received %d lease break(s)!!\n", + lease_break_info.count); + } + CHECK_VAL(lease_break_info.count, 1); + CHECK_BREAK_INFO("RHW", "RH", LEASE2F3); + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_util_close(tree1, h_client1_file1); + smb2_util_close(tree1, h_client1_file2); + smb2_util_close(tree1, h_client1_file3); + + /* + * Session 2 still has RW lease on file 1. Deletion of this file by 1 + * leads to a lease break call to session 2 file1 + */ + smb2_util_unlink(tree1, fname1); + /* + * Bug - Samba does not revoke Handle lease on unlink + * CHECK_BREAK_INFO("RH", "R", LEASE2F1); + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * Session 2 still has RW lease on file 2. Deletion of this file by 1 + * leads to a lease break call to session 2 file2 + */ + smb2_util_unlink(tree1, fname2); + /* + * Bug - Samba does not revoke Handle lease on unlink + * CHECK_BREAK_INFO("RH", "R", LEASE2F2); + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + + /* + * Session 2 still has RW lease on file 3. Deletion of this file by 1 + * leads to a lease break call to session 2 file3 + */ + smb2_util_unlink(tree1, fname3); + /* + * Bug - Samba does not revoke Handle lease on unlink + * CHECK_BREAK_INFO("RH", "R", LEASE2F3); + */ + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_util_close(tree2C, h_client2_file1); + smb2_util_close(tree2C, h_client2_file2); + smb2_util_close(tree2C, h_client2_file3); + + test_multichannel_free_channels(tree2A, tree2B, tree2C); + tree2A = tree2B = tree2C = NULL; + +done: + if (block_ok && !unblock_ok) { + test_unblock_smb2_transport(tctx, transport2A); + } + if (block_setup) { + test_cleanup_blocked_transports(tctx); + } + + tree1->session = session1; + + smb2_util_close(tree1, h_client1_file1); + smb2_util_close(tree1, h_client1_file2); + smb2_util_close(tree1, h_client1_file3); + if (tree2A != NULL) { + smb2_util_close(tree2A, h_client2_file1); + smb2_util_close(tree2A, h_client2_file2); + smb2_util_close(tree2A, h_client2_file3); + } + + if (h != NULL) { + smb2_util_close(tree1, *h); + } + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + smb2_util_unlink(tree1, fname3); + smb2_deltree(tree1, BASEDIR); + + test_multichannel_free_channels(tree2A, tree2B, tree2C); + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/* + * Test 3: Check to see how the server behaves if lease break + * response is sent over a different channel to one over which + * the break is received. + * Connect 2A, 2B + * open file1 in session 2A + * open file1 in session 1 + * Lease break sent to 2A + * 2B sends back lease break reply. + * session 1 allowed to open file + */ +static bool test_multichannel_lease_break_test3(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_create io1; + bool ret = true; + const char *fname1 = BASEDIR "\\lease_break_test1.dat"; + struct smb2_tree *tree2A = NULL; + struct smb2_tree *tree2B = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2A = NULL; + struct smbcli_options transport2_options; + uint16_t local_port = 0; + struct smb2_lease ls1; + struct tevent_timer *te = NULL; + struct timeval ne; + bool timesup = false; + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Lease break retry: Test3\n"); + + torture_reset_lease_break_info(tctx, &lease_break_info); + + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + CHECK_VAL(lease_break_info.count, 0); + + smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1, + smb2_util_lease_state("RHW")); + test_multichannel_init_smb_create(&io1); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channels(tctx, host, share, + credentials, + &transport2_options, + &tree2A, &tree2B, NULL); + torture_assert(tctx, ret, "Could not create channels.\n"); + transport2A = tree2A->session->transport; + transport2A->lease.private_data = tree2B; + + /* 2a opens file1 */ + torture_comment(tctx, "client2 opens fname1 via session 2A\n"); + smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1, + smb2_util_lease_state("RHW")); + status = smb2_create(tree2A, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RHW", true, LEASE2F1, 0); + CHECK_VAL(io1.out.durable_open_v2, false); //true); + CHECK_VAL(io1.out.timeout, io1.in.timeout); + CHECK_VAL(io1.out.durable_open, false); + CHECK_VAL(lease_break_info.count, 0); + + /* Set a timeout for 5 seconds for session 1 to open file1 */ + ne = tevent_timeval_current_ofs(0, 5000000); + te = tevent_add_timer(tctx->ev, tmp_ctx, ne, timeout_cb, ×up); + if (te == NULL) { + torture_comment(tctx, "Failed to add timer."); + goto done; + } + + /* 1 opens file2 */ + torture_comment(tctx, "Client opens fname1 with session 1\n"); + smb2_lease_create(&io1, &ls1, false, fname1, LEASE1F1, + smb2_util_lease_state("RHW")); + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io1, "RH", true, LEASE1F1, 0); + CHECK_VAL(io1.out.durable_open_v2, false); + CHECK_VAL(io1.out.timeout, 0); + CHECK_VAL(io1.out.durable_open, false); + + CHECK_VAL(lease_break_info.count, 1); + CHECK_BREAK_INFO("RHW", "RH", LEASE2F1); + + /* + * Check if timeout handler was fired. This would indicate + * that the server didn't receive a reply for the oplock break + * from the client and the server let session 1 open the file + * only after the oplock break timeout. + */ + CHECK_VAL(timesup, false); + +done: + smb2_util_close(tree1, h_client1_file1); + if (tree2A != NULL) { + smb2_util_close(tree2A, h_client2_file1); + } + + if (h != NULL) { + smb2_util_close(tree1, *h); + } + + smb2_util_unlink(tree1, fname1); + smb2_deltree(tree1, BASEDIR); + + test_multichannel_free_channels(tree2A, tree2B, NULL); + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/* + * Test limits of channels + */ +static bool test_multichannel_num_channels(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + TALLOC_CTX *mem_ctx = talloc_new(tctx); + bool ret = true; + struct smb2_tree **tree2 = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport **transport2 = NULL; + struct smbcli_options transport2_options; + struct smb2_session **session2 = NULL; + uint32_t server_capabilities; + int i; + int max_channels = 33; /* 32 is the W2K12R2 and W2K16 limit */ + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_fail(tctx, + "SMB 3.X Dialect family required for Multichannel" + " tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_fail(tctx, + "Server does not support multichannel."); + } + + torture_comment(tctx, "Testing max. number of channels\n"); + + transport2_options = transport1->options; + transport2_options.client_guid = GUID_random(); + + tree2 = talloc_zero_array(mem_ctx, struct smb2_tree *, + max_channels); + transport2 = talloc_zero_array(mem_ctx, struct smb2_transport *, + max_channels); + session2 = talloc_zero_array(mem_ctx, struct smb2_session *, + max_channels); + if (tree2 == NULL || transport2 == NULL || session2 == NULL) { + torture_fail(tctx, "out of memory"); + } + + for (i = 0; i < max_channels; i++) { + + NTSTATUS expected_status; + + torture_assert_ntstatus_ok_goto(tctx, + smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2[i], + tctx->ev, + &transport2_options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ), + ret, done, "smb2_connect failed"); + + transport2[i] = tree2[i]->session->transport; + + if (i == 0) { + /* + * done for the 1st channel + * + * For all remaining channels we do the + * session setup on our own. + */ + transport2_options.only_negprot = true; + continue; + } + + /* + * Now bind the session2[i] to the transport2 + */ + session2[i] = smb2_session_channel(transport2[i], + lpcfg_gensec_settings(tctx, + tctx->lp_ctx), + tree2[0], + tree2[0]->session); + + torture_assert(tctx, session2[i] != NULL, + "smb2_session_channel failed"); + + torture_comment(tctx, "established transport2 [#%d]\n", i); + + if (i >= 32) { + expected_status = NT_STATUS_INSUFFICIENT_RESOURCES; + } else { + expected_status = NT_STATUS_OK; + } + + torture_assert_ntstatus_equal_goto(tctx, + smb2_session_setup_spnego(session2[i], + samba_cmdline_get_creds(), + 0 /* previous_session_id */), + expected_status, + ret, done, + talloc_asprintf(tctx, "failed to establish session " + "setup for channel #%d", i)); + + torture_comment(tctx, "bound session2 [#%d] to session2 [0]\n", + i); + } + + done: + talloc_free(mem_ctx); + + return ret; +} + +struct test_multichannel_lease_break_state; + +struct test_multichannel_lease_break_channel { + struct test_multichannel_lease_break_state *state; + size_t idx; + char name[64]; + struct smb2_tree *tree; + bool blocked; + struct timeval break_time; + double full_duration; + double relative_duration; + struct smb2_lease_break lb; + size_t break_num; +}; + +struct test_multichannel_lease_break_state { + struct torture_context *tctx; + struct timeval open_req_time; + struct timeval open_rep_time; + size_t num_breaks; + struct timeval last_break_time; + struct test_multichannel_lease_break_channel channels[32]; +}; + +static bool test_multichannel_lease_break_handler(struct smb2_transport *transport, + const struct smb2_lease_break *lb, + void *private_data) +{ + struct test_multichannel_lease_break_channel *c = + (struct test_multichannel_lease_break_channel *)private_data; + struct test_multichannel_lease_break_state *state = c->state; + + c->break_time = timeval_current(); + c->full_duration = timeval_elapsed2(&state->open_req_time, + &c->break_time); + c->relative_duration = timeval_elapsed2(&state->last_break_time, + &c->break_time); + state->last_break_time = c->break_time; + c->lb = *lb; + c->break_num = ++state->num_breaks; + + torture_comment(state->tctx, "Got LEASE break epoch[0x%x] %zu on %s after %f ( %f)\n", + c->lb.new_epoch, c->break_num, c->name, + c->relative_duration, + c->full_duration); + + return torture_lease_handler(transport, lb, c->tree); +} + +static bool test_multichannel_lease_break_test4(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct test_multichannel_lease_break_state state = { + .tctx = tctx, + }; + struct test_multichannel_lease_break_channel *open2_channel = NULL; + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_handle h_client1_file1 = {{0}}; + struct smb2_handle h_client2_file1 = {{0}}; + struct smb2_create io1; + struct smb2_create io2; + bool ret = true; + const char *fname1 = BASEDIR "\\lease_break_test4.dat"; + struct smb2_tree *trees2[32] = { NULL, }; + size_t i; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options transport2_options; + struct smb2_session *session1 = tree1->session; + uint16_t local_port = 0; + struct smb2_lease ls1; + struct smb2_lease ls2; + bool block_setup = false; + bool block_ok = false; + double open_duration; + + if (!test_multichannel_initial_checks(tctx, tree1)) { + return true; + } + + torture_comment(tctx, "Lease break retry: Test4\n"); + + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + torture_comment(tctx, "transport1 [%p]\n", transport1); + local_port = torture_get_local_port_from_transport(transport1); + torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port); + + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname1); + CHECK_VAL(lease_break_info.count, 0); + + smb2_lease_v2_create(&io2, &ls2, false, fname1, + LEASE2F1, NULL, + smb2_util_lease_state("RHW"), + 0x20); + + transport2_options = transport1->options; + + ret = test_multichannel_create_channel_array(tctx, host, share, credentials, + &transport2_options, + ARRAY_SIZE(trees2), trees2); + torture_assert(tctx, ret, "Could not create channels.\n"); + + for (i = 0; i < ARRAY_SIZE(trees2); i++) { + struct test_multichannel_lease_break_channel *c = &state.channels[i]; + struct smb2_transport *t = trees2[i]->session->transport; + + c->state = &state; + c->idx = i+1; + c->tree = trees2[i]; + snprintf(c->name, sizeof(c->name), "trees2_%zu", c->idx); + + t->lease.handler = test_multichannel_lease_break_handler; + t->lease.private_data = c; + } + + open2_channel = &state.channels[0]; + + /* 2a opens file1 */ + torture_comment(tctx, "client2 opens fname1 via %s\n", + open2_channel->name); + status = smb2_create(open2_channel->tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h_client2_file1 = io2.out.file.handle; + CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io2, "RHW", true, LEASE2F1, 0, 0, 0x21); + CHECK_VAL(io2.out.durable_open_v2, false); + CHECK_VAL(io2.out.timeout, io2.in.timeout); + CHECK_VAL(io2.out.durable_open, false); + CHECK_VAL(lease_break_info.count, 0); + + block_setup = test_setup_blocked_transports(tctx); + torture_assert(tctx, block_setup, "test_setup_blocked_transports"); + + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + struct test_multichannel_lease_break_channel *c = &state.channels[i]; + struct smb2_transport *t = c->tree->session->transport; + + torture_comment(tctx, "Blocking %s\n", c->name); + block_ok = _test_block_smb2_transport(tctx, t, c->name); + torture_assert_goto(tctx, block_ok, ret, done, "we could not block tcp transport"); + c->blocked = true; + } + + /* 1 opens file2 */ + torture_comment(tctx, + "Client opens fname1 with session 1 with all %zu blocked\n", + ARRAY_SIZE(trees2)); + smb2_lease_v2_create(&io1, &ls1, false, fname1, + LEASE1F1, NULL, + smb2_util_lease_state("RHW"), + 0x10); + CHECK_VAL(lease_break_info.count, 0); + state.open_req_time = timeval_current(); + state.last_break_time = state.open_req_time; + status = smb2_create(tree1, mem_ctx, &io1); + state.open_rep_time = timeval_current(); + CHECK_STATUS(status, NT_STATUS_OK); + h_client1_file1 = io1.out.file.handle; + + CHECK_VAL_GREATER_THAN(lease_break_info.count, 1); + + open_duration = timeval_elapsed2(&state.open_req_time, + &state.open_rep_time); + torture_comment(tctx, "open_duration: %f\n", open_duration); + if (lease_break_info.count < ARRAY_SIZE(state.channels)) { + CHECK_VAL_GREATER_THAN(open_duration, 35); + CHECK_LEASE_V2(&io1, "RH", true, LEASE1F1, 0, 0, 0x11); + } else { + CHECK_LEASE_V2(&io1, "RWH", true, LEASE1F1, 0, 0, 0x11); + } + + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + if (lease_break_info.count >= ARRAY_SIZE(state.channels)) { + break; + } + torture_comment(tctx, "Received %d lease break(s) wait for more!!\n", + lease_break_info.count); + torture_wait_for_lease_break(tctx); + } + + if (lease_break_info.count == 0) { + torture_comment(tctx, + "Did not receive expected lease break!!\n"); + } else { + torture_comment(tctx, "Received %d lease break(s)!!\n", + lease_break_info.count); + } + + if (lease_break_info.count < ARRAY_SIZE(state.channels)) { + CHECK_VAL_GREATER_THAN(lease_break_info.count, 3); + } else { + CHECK_VAL(lease_break_info.count, ARRAY_SIZE(state.channels)); + } + + for (i = 0; i < lease_break_info.count; i++) { + struct test_multichannel_lease_break_channel *c = &state.channels[i]; + + torture_comment(tctx, "Verify %s\n", c->name); + torture_assert_int_equal(tctx, c->break_num, c->idx, + "Got lease break in wrong order"); + CHECK_LEASE_BREAK_V2(c->lb, LEASE2F1, "RWH", "RH", + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED, + 0x22); + } + +done: + for (i = 0; i < ARRAY_SIZE(state.channels); i++) { + struct test_multichannel_lease_break_channel *c = &state.channels[i]; + struct smb2_transport *t = NULL; + + if (!c->blocked) { + continue; + } + + t = c->tree->session->transport; + + torture_comment(tctx, "Unblocking %s\n", c->name); + _test_unblock_smb2_transport(tctx, t, c->name); + c->blocked = false; + } + if (block_setup) { + test_cleanup_blocked_transports(tctx); + } + + tree1->session = session1; + + smb2_util_close(tree1, h_client1_file1); + if (trees2[0] != NULL) { + smb2_util_close(trees2[0], h_client2_file1); + } + + if (h != NULL) { + smb2_util_close(tree1, *h); + } + + smb2_util_unlink(tree1, fname1); + smb2_deltree(tree1, BASEDIR); + + for (i = 0; i < ARRAY_SIZE(trees2); i++) { + if (trees2[i] == NULL) { + continue; + } + TALLOC_FREE(trees2[i]); + } + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/* + * Test channel merging race + * This is a regression test for + * https://bugzilla.samba.org/show_bug.cgi?id=15346 + */ +struct test_multichannel_bug_15346_conn; + +struct test_multichannel_bug_15346_state { + struct torture_context *tctx; + struct test_multichannel_bug_15346_conn *conns; + size_t num_conns; + size_t num_ready; + bool asserted; + bool looping; +}; + +struct test_multichannel_bug_15346_conn { + struct test_multichannel_bug_15346_state *state; + size_t idx; + struct smbXcli_conn *smbXcli; + struct tevent_req *nreq; + struct tevent_req *ereq; +}; + +static void test_multichannel_bug_15346_ndone(struct tevent_req *subreq); +static void test_multichannel_bug_15346_edone(struct tevent_req *subreq); + +static void test_multichannel_bug_15346_ndone(struct tevent_req *subreq) +{ + struct test_multichannel_bug_15346_conn *conn = + (struct test_multichannel_bug_15346_conn *) + tevent_req_callback_data_void(subreq); + struct test_multichannel_bug_15346_state *state = conn->state; + struct torture_context *tctx = state->tctx; + NTSTATUS status; + bool ok = false; + + SMB_ASSERT(conn->nreq == subreq); + conn->nreq = NULL; + + status = smbXcli_negprot_recv(subreq, NULL, NULL); + TALLOC_FREE(subreq); + torture_assert_ntstatus_ok_goto(tctx, status, ok, asserted, + "smbXcli_negprot_recv failed"); + + torture_comment(tctx, "conn[%zu]: negprot done\n", conn->idx); + + conn->ereq = smb2cli_echo_send(conn->smbXcli, + tctx->ev, + conn->smbXcli, + state->num_conns * 2 * 1000); + torture_assert_goto(tctx, conn->ereq != NULL, ok, asserted, + "smb2cli_echo_send"); + tevent_req_set_callback(conn->ereq, + test_multichannel_bug_15346_edone, + conn); + + return; + +asserted: + SMB_ASSERT(!ok); + state->asserted = true; + state->looping = false; + return; +} + +static void test_multichannel_bug_15346_edone(struct tevent_req *subreq) +{ + struct test_multichannel_bug_15346_conn *conn = + (struct test_multichannel_bug_15346_conn *) + tevent_req_callback_data_void(subreq); + struct test_multichannel_bug_15346_state *state = conn->state; + struct torture_context *tctx = state->tctx; + NTSTATUS status; + bool ok = false; + + SMB_ASSERT(conn->ereq == subreq); + conn->ereq = NULL; + + status = smb2cli_echo_recv(subreq); + TALLOC_FREE(subreq); + torture_assert_ntstatus_ok_goto(tctx, status, ok, asserted, + "smb2cli_echo_recv failed"); + + torture_comment(tctx, "conn[%zu]: echo done\n", conn->idx); + + state->num_ready += 1; + if (state->num_ready < state->num_conns) { + return; + } + + state->looping = false; + return; + +asserted: + SMB_ASSERT(!ok); + state->asserted = true; + state->looping = false; + return; +} + +static bool test_multichannel_bug_15346(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct resolve_context *resolve_ctx = lpcfg_resolve_context(tctx->lp_ctx); + const char *socket_options = lpcfg_socket_options(tctx->lp_ctx); + struct gensec_settings *gsettings = NULL; + bool ret = true; + NTSTATUS status; + struct smb2_transport *transport1 = tree1->session->transport; + struct test_multichannel_bug_15346_state *state = NULL; + uint32_t server_capabilities; + struct smb2_handle root_handle = {{0}}; + size_t i; + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_fail(tctx, + "SMB 3.X Dialect family required for Multichannel" + " tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_fail(tctx, + "Server does not support multichannel."); + } + + torture_comment(tctx, "Testing for BUG 15346\n"); + + state = talloc_zero(tctx, struct test_multichannel_bug_15346_state); + torture_assert_goto(tctx, state != NULL, ret, done, + "talloc_zero"); + state->tctx = tctx; + + gsettings = lpcfg_gensec_settings(state, tctx->lp_ctx); + torture_assert_goto(tctx, gsettings != NULL, ret, done, + "lpcfg_gensec_settings"); + + /* + * 32 is the W2K12R2 and W2K16 limit + * add 31 additional connections + */ + state->num_conns = 31; + state->conns = talloc_zero_array(state, + struct test_multichannel_bug_15346_conn, + state->num_conns); + torture_assert_goto(tctx, state->conns != NULL, ret, done, + "talloc_zero_array"); + + /* + * First we open additional the tcp connections + */ + + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + struct socket_context *sock = NULL; + uint16_t port = 445; + struct smbcli_options options = transport1->options; + + conn->state = state; + conn->idx = i; + + status = socket_connect_multi(state->conns, + host, + 1, &port, + resolve_ctx, + tctx->ev, + &sock, + &port); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "socket_connect_multi failed"); + + conn->smbXcli = smbXcli_conn_create(state->conns, + sock->fd, + host, + SMB_SIGNING_OFF, + 0, + &options.client_guid, + options.smb2_capabilities, + &options.smb3_capabilities); + torture_assert_goto(tctx, conn->smbXcli != NULL, ret, done, + "smbXcli_conn_create failed"); + sock->fd = -1; + TALLOC_FREE(sock); + } + + /* + * Now prepare the async SMB2 Negotiate requests + */ + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + + conn->nreq = smbXcli_negprot_send(conn->smbXcli, + tctx->ev, + conn->smbXcli, + state->num_conns * 2 * 1000, + smbXcli_conn_protocol(transport1->conn), + smbXcli_conn_protocol(transport1->conn), + 33, /* max_credits */ + NULL); + torture_assert_goto(tctx, conn->nreq != NULL, ret, done, "smbXcli_negprot_send"); + tevent_req_set_callback(conn->nreq, + test_multichannel_bug_15346_ndone, + conn); + } + + /* + * now we loop until all negprot and the first round + * of echos are done. + */ + state->looping = true; + while (state->looping) { + torture_assert_goto(tctx, tevent_loop_once(tctx->ev) == 0, + ret, done, "tevent_loop_once"); + } + + if (state->asserted) { + ret = false; + goto done; + } + + /* + * No we check that the connections are still usable + */ + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + + torture_comment(tctx, "conn[%zu]: checking echo again1\n", conn->idx); + + status = smb2cli_echo(conn->smbXcli, 1000); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2cli_echo failed"); + } + + status = smb2_util_roothandle(tree1, &root_handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_roothandle failed"); + + /* + * No we check that the connections are still usable + */ + for (i = 0; i < state->num_conns; i++) { + struct test_multichannel_bug_15346_conn *conn = &state->conns[i]; + struct smbcli_options options = transport1->options; + struct smb2_session *session = NULL; + struct smb2_tree *tree = NULL; + union smb_fileinfo io; + + torture_comment(tctx, "conn[%zu]: checking session bind\n", conn->idx); + + /* + * Prepare smb2_{tree,session,transport} structures + * for the existing connection. + */ + options.only_negprot = true; + status = smb2_connect_ext(state->conns, + host, + NULL, /* ports */ + share, + resolve_ctx, + samba_cmdline_get_creds(), + &conn->smbXcli, + 0, /* previous_session_id */ + &tree, + tctx->ev, + &options, + socket_options, + gsettings); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect_ext failed"); + conn->smbXcli = tree->session->transport->conn; + + session = smb2_session_channel(tree->session->transport, + lpcfg_gensec_settings(tree, tctx->lp_ctx), + tree, + tree1->session); + torture_assert_goto(tctx, session != NULL, ret, done, + "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* + * Fix up the bound smb2_tree + */ + tree->session = session; + tree->smbXcli = tree1->smbXcli; + + ZERO_STRUCT(io); + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = root_handle; + + status = smb2_getinfo_file(tree, tree, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + } + + done: + talloc_free(state); + + return ret; +} + +struct torture_suite *torture_smb2_multichannel_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "multichannel"); + struct torture_suite *suite_generic = torture_suite_create(ctx, + "generic"); + struct torture_suite *suite_oplocks = torture_suite_create(ctx, + "oplocks"); + struct torture_suite *suite_leases = torture_suite_create(ctx, + "leases"); + struct torture_suite *suite_bugs = torture_suite_create(ctx, + "bugs"); + + torture_suite_add_suite(suite, suite_generic); + torture_suite_add_suite(suite, suite_oplocks); + torture_suite_add_suite(suite, suite_leases); + torture_suite_add_suite(suite, suite_bugs); + + torture_suite_add_1smb2_test(suite_generic, "interface_info", + test_multichannel_interface_info); + torture_suite_add_1smb2_test(suite_generic, "num_channels", + test_multichannel_num_channels); + torture_suite_add_1smb2_test(suite_oplocks, "test1", + test_multichannel_oplock_break_test1); + torture_suite_add_1smb2_test(suite_oplocks, "test2", + test_multichannel_oplock_break_test2); + torture_suite_add_1smb2_test(suite_oplocks, "test3_windows", + test_multichannel_oplock_break_test3_windows); + torture_suite_add_1smb2_test(suite_oplocks, "test3_specification", + test_multichannel_oplock_break_test3_specification); + torture_suite_add_1smb2_test(suite_leases, "test1", + test_multichannel_lease_break_test1); + torture_suite_add_1smb2_test(suite_leases, "test2", + test_multichannel_lease_break_test2); + torture_suite_add_1smb2_test(suite_leases, "test3", + test_multichannel_lease_break_test3); + torture_suite_add_1smb2_test(suite_leases, "test4", + test_multichannel_lease_break_test4); + torture_suite_add_1smb2_test(suite_bugs, "bug_15346", + test_multichannel_bug_15346); + + suite->description = talloc_strdup(suite, "SMB2 Multichannel tests"); + + return suite; +} diff --git a/source4/torture/smb2/notify.c b/source4/torture/smb2/notify.c new file mode 100644 index 0000000..0aadc50 --- /dev/null +++ b/source4/torture/smb2/notify.c @@ -0,0 +1,2786 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 notify test suite + + Copyright (C) Stefan Metzmacher 2006 + Copyright (C) Andrew Tridgell 2009 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "torture/util.h" + +#include "system/filesys.h" +#include "auth/credentials/credentials.h" +#include "lib/cmdline/cmdline.h" +#include "librpc/gen_ndr/security.h" + +#include "lib/events/events.h" + +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(torture, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(torture, TORTURE_FAIL, \ + "(%s) wrong value for %s 0x%x should be 0x%x\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_WIRE_STR(field, value) do { \ + if (!field.s || strcmp(field.s, value)) { \ + torture_result(torture, TORTURE_FAIL, \ + "(%s) %s [%s] != %s\n", __location__, #field, \ + field.s, value); \ + ret = false; \ + goto done; \ + }} while (0) + +#define WAIT_FOR_ASYNC_RESPONSE(req) \ + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { \ + if (tevent_loop_once(torture->ev) != 0) { \ + break; \ + } \ + } + +#define BASEDIR "test_notify" +#define FNAME "smb2-notify01.dat" + +static bool test_valid_request(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle dh; + struct smb2_notify n; + struct smb2_request *req; + uint32_t max_buffer_size; + + torture_comment(torture, "TESTING VALIDITY OF CHANGE NOTIFY REQUEST\n"); + + smb2_transport_credits_ask_num(tree->session->transport, 256); + + smb2_util_unlink(tree, FNAME); + + status = smb2_util_roothandle(tree, &dh); + CHECK_STATUS(status, NT_STATUS_OK); + + max_buffer_size = + smb2cli_conn_max_trans_size(tree->session->transport->conn); + + n.in.recursive = 0x0000; + n.in.buffer_size = max_buffer_size; + n.in.file.handle = dh; + n.in.completion_filter = FILE_NOTIFY_CHANGE_ALL; + n.in.unknown = 0x00000000; + req = smb2_notify_send(tree, &n); + + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { + if (tevent_loop_once(torture->ev) != 0) { + break; + } + } + + status = torture_setup_simple_file(torture, tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &n); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(n.out.num_changes, 1); + CHECK_VAL(n.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(n.out.changes[0].name, FNAME); + + /* + * if the change response doesn't fit in the buffer + * NOTIFY_ENUM_DIR is returned. + */ + n.in.buffer_size = 0x00000000; + req = smb2_notify_send(tree, &n); + + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { + if (tevent_loop_once(torture->ev) != 0) { + break; + } + } + + status = torture_setup_simple_file(torture, tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &n); + CHECK_STATUS(status, NT_STATUS_NOTIFY_ENUM_DIR); + + /* + * if the change response fits in the buffer we get + * NT_STATUS_OK again + */ + n.in.buffer_size = max_buffer_size; + req = smb2_notify_send(tree, &n); + + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { + if (tevent_loop_once(torture->ev) != 0) { + break; + } + } + + status = torture_setup_simple_file(torture, tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &n); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(n.out.num_changes, 3); + CHECK_VAL(n.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(n.out.changes[0].name, FNAME); + CHECK_VAL(n.out.changes[1].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(n.out.changes[1].name, FNAME); + CHECK_VAL(n.out.changes[2].action, NOTIFY_ACTION_MODIFIED); + CHECK_WIRE_STR(n.out.changes[2].name, FNAME); + + /* if the first notify returns NOTIFY_ENUM_DIR, all do */ + status = smb2_util_close(tree, dh); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_roothandle(tree, &dh); + CHECK_STATUS(status, NT_STATUS_OK); + + n.in.recursive = 0x0000; + n.in.buffer_size = 0x00000001; + n.in.file.handle = dh; + n.in.completion_filter = FILE_NOTIFY_CHANGE_ALL; + n.in.unknown = 0x00000000; + req = smb2_notify_send(tree, &n); + + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { + if (tevent_loop_once(torture->ev) != 0) { + break; + } + } + + status = torture_setup_simple_file(torture, tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &n); + CHECK_STATUS(status, NT_STATUS_NOTIFY_ENUM_DIR); + + n.in.buffer_size = max_buffer_size; + req = smb2_notify_send(tree, &n); + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { + if (tevent_loop_once(torture->ev) != 0) { + break; + } + } + + status = torture_setup_simple_file(torture, tree, FNAME); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &n); + CHECK_STATUS(status, NT_STATUS_NOTIFY_ENUM_DIR); + + /* if the buffer size is too large, we get invalid parameter */ + n.in.recursive = 0x0000; + n.in.buffer_size = max_buffer_size + 1; + n.in.file.handle = dh; + n.in.completion_filter = FILE_NOTIFY_CHANGE_ALL; + n.in.unknown = 0x00000000; + req = smb2_notify_send(tree, &n); + status = smb2_notify_recv(req, torture, &n); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + +done: + return ret; +} + +/* + basic testing of change notify on directories +*/ + +#define BASEDIR_DIR BASEDIR "_DIR" + +static bool torture_smb2_notify_dir(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + union smb_close cl; + int i, count; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_request *req, *req2; + const char *fname = BASEDIR_DIR "\\subdir-name"; + extern int torture_numops; + + torture_comment(torture, "TESTING CHANGE NOTIFY ON DIRECTORIES\n"); + + smb2_deltree(tree1, BASEDIR_DIR); + smb2_util_rmdir(tree1, BASEDIR_DIR); + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_DIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ; + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + torture_comment(torture, "Testing notify cancel\n"); + + req = smb2_notify_send(tree1, &(notify.smb2)); + smb2_cancel(req); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + torture_comment(torture, "Testing notify mkdir\n"); + + req = smb2_notify_send(tree1, &(notify.smb2)); + smb2_util_mkdir(tree2, fname); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, "Testing notify rmdir\n"); + + req = smb2_notify_send(tree1, &(notify.smb2)); + smb2_util_rmdir(tree2, fname); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, + "Testing notify mkdir - rmdir - mkdir - rmdir\n"); + + smb2_util_mkdir(tree2, fname); + smb2_util_rmdir(tree2, fname); + smb2_util_mkdir(tree2, fname); + smb2_util_rmdir(tree2, fname); + smb_msleep(200); + req = smb2_notify_send(tree1, &(notify.smb2)); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 4); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + CHECK_VAL(notify.smb2.out.changes[1].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[1].name, "subdir-name"); + CHECK_VAL(notify.smb2.out.changes[2].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[2].name, "subdir-name"); + CHECK_VAL(notify.smb2.out.changes[3].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[3].name, "subdir-name"); + + count = torture_numops; + torture_comment(torture, + "Testing buffered notify on create of %d files\n", count); + for (i=0;i<count;i++) { + struct smb2_handle h12; + char *fname2 = talloc_asprintf(torture, + BASEDIR_DIR "\\test%d.txt", + i); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname2; + + status = smb2_create(tree1, torture, &(io.smb2)); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(torture, "Failed to create %s \n", + fname); + ret = false; + goto done; + } + h12 = io.smb2.out.file.handle; + talloc_free(fname2); + smb2_util_close(tree1, h12); + } + + /* (1st notify) setup a new notify on a different directory handle. + This new notify won't see the events above. */ + notify.smb2.in.file.handle = h2; + req2 = smb2_notify_send(tree1, &(notify.smb2)); + + /* (2nd notify) whereas this notify will see the above buffered events, + and it directly returns the buffered events */ + notify.smb2.in.file.handle = h1; + req = smb2_notify_send(tree1, &(notify.smb2)); + + status = smb2_util_unlink(tree1, BASEDIR_DIR "\\nonexistent.txt"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + /* (1st unlink) as the 2nd notify directly returns, + this unlink is only seen by the 1st notify and + the 3rd notify (later) */ + torture_comment(torture, + "Testing notify on unlink for the first file\n"); + status = smb2_util_unlink(tree2, BASEDIR_DIR "\\test0.txt"); + CHECK_STATUS(status, NT_STATUS_OK); + + /* receive the reply from the 2nd notify */ + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, count); + for (i=1;i<count;i++) { + CHECK_VAL(notify.smb2.out.changes[i].action, + NOTIFY_ACTION_ADDED); + } + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "test0.txt"); + + torture_comment(torture, "and now from the 1st notify\n"); + status = smb2_notify_recv(req2, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "test0.txt"); + + torture_comment(torture, + "(3rd notify) this notify will only see the 1st unlink\n"); + req = smb2_notify_send(tree1, &(notify.smb2)); + + status = smb2_util_unlink(tree1, BASEDIR_DIR "\\nonexistent.txt"); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + for (i=1;i<count;i++) { + char *fname2 = talloc_asprintf(torture, + BASEDIR_DIR "\\test%d.txt", i); + status = smb2_util_unlink(tree2, fname2); + CHECK_STATUS(status, NT_STATUS_OK); + talloc_free(fname2); + } + + /* receive the 3rd notify */ + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "test0.txt"); + + /* and we now see the rest of the unlink calls on both + * directory handles */ + notify.smb2.in.file.handle = h1; + sleep(3); + req = smb2_notify_send(tree1, &(notify.smb2)); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, count-1); + for (i=0;i<notify.smb2.out.num_changes;i++) { + CHECK_VAL(notify.smb2.out.changes[i].action, + NOTIFY_ACTION_REMOVED); + } + notify.smb2.in.file.handle = h2; + req = smb2_notify_send(tree1, &(notify.smb2)); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, count-1); + for (i=0;i<notify.smb2.out.num_changes;i++) { + CHECK_VAL(notify.smb2.out.changes[i].action, + NOTIFY_ACTION_REMOVED); + } + + torture_comment(torture, + "Testing if a close() on the dir handle triggers the notify reply\n"); + + notify.smb2.in.file.handle = h1; + req = smb2_notify_send(tree1, &(notify.smb2)); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_NOTIFY_CLEANUP); + CHECK_VAL(notify.smb2.out.num_changes, 9); + +done: + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h2); + smb2_deltree(tree1, BASEDIR_DIR); + return ret; +} + +static struct smb2_handle custom_smb2_create(struct smb2_tree *tree, + struct torture_context *torture, + struct smb2_create *smb2) +{ + struct smb2_handle h1; + bool ret = true; + NTSTATUS status; + smb2_deltree(tree, smb2->in.fname); + status = smb2_create(tree, torture, smb2); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = smb2->out.file.handle; +done: + if (!ret) { + h1 = (struct smb2_handle) { + .data = { 0 , 0}, + }; + } + return h1; +} + +/* + testing of recursive change notify +*/ + +#define BASEDIR_REC BASEDIR "_REC" + +static bool torture_smb2_notify_recursive(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io, io1; + union smb_setfileinfo sinfo; + struct smb2_handle h1; + struct smb2_request *req1, *req2; + + smb2_deltree(tree1, BASEDIR_REC); + smb2_util_rmdir(tree1, BASEDIR_REC); + + torture_comment(torture, "TESTING CHANGE NOTIFY WITH RECURSION\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_REC; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, on file or directory name + changes. Setup both with and without recursion */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_CREATION; + notify.smb2.in.file.handle = h1; + + notify.smb2.in.recursive = true; + req1 = smb2_notify_send(tree1, &(notify.smb2)); + smb2_cancel(req1); + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + notify.smb2.in.recursive = false; + req2 = smb2_notify_send(tree1, &(notify.smb2)); + smb2_cancel(req2); + status = smb2_notify_recv(req2, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + ZERO_STRUCT(io1.smb2); + io1.generic.level = RAW_OPEN_SMB2; + io1.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io1.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE| + SEC_RIGHTS_FILE_ALL; + io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io1.smb2.in.alloc_size = 0; + io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io1.smb2.in.security_flags = 0; + io1.smb2.in.fname = BASEDIR_REC "\\subdir-name"; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree2, io1.smb2.out.file.handle); + + io1.smb2.in.fname = BASEDIR_REC "\\subdir-name\\subname1"; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR_REC "\\subdir-name\\subname1-r"; + status = smb2_setinfo_file(tree2, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io1.smb2.in.fname = BASEDIR_REC "\\subdir-name\\subname2"; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = BASEDIR_REC "\\subname2-r"; + status = smb2_setinfo_file(tree2, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + io1.smb2.in.fname = BASEDIR_REC "\\subname2-r"; + io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = BASEDIR_REC "\\subname3-r"; + status = smb2_setinfo_file(tree2, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + notify.smb2.in.completion_filter = 0; + notify.smb2.in.recursive = true; + smb_msleep(200); + req1 = smb2_notify_send(tree1, &(notify.smb2)); + + status = smb2_util_rmdir(tree2, + BASEDIR_REC "\\subdir-name\\subname1-r"); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_rmdir(tree2, + BASEDIR_REC "\\subdir-name"); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_unlink(tree2, BASEDIR_REC "\\subname3-r"); + CHECK_STATUS(status, NT_STATUS_OK); + + notify.smb2.in.recursive = false; + req2 = smb2_notify_send(tree1, &(notify.smb2)); + + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 9); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + CHECK_VAL(notify.smb2.out.changes[1].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[1].name, "subdir-name\\subname1"); + CHECK_VAL(notify.smb2.out.changes[2].action, NOTIFY_ACTION_OLD_NAME); + CHECK_WIRE_STR(notify.smb2.out.changes[2].name, "subdir-name\\subname1"); + CHECK_VAL(notify.smb2.out.changes[3].action, NOTIFY_ACTION_NEW_NAME); + CHECK_WIRE_STR(notify.smb2.out.changes[3].name, "subdir-name\\subname1-r"); + CHECK_VAL(notify.smb2.out.changes[4].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[4].name, "subdir-name\\subname2"); + CHECK_VAL(notify.smb2.out.changes[5].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[5].name, "subdir-name\\subname2"); + CHECK_VAL(notify.smb2.out.changes[6].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[6].name, "subname2-r"); + CHECK_VAL(notify.smb2.out.changes[7].action, NOTIFY_ACTION_OLD_NAME); + CHECK_WIRE_STR(notify.smb2.out.changes[7].name, "subname2-r"); + CHECK_VAL(notify.smb2.out.changes[8].action, NOTIFY_ACTION_NEW_NAME); + CHECK_WIRE_STR(notify.smb2.out.changes[8].name, "subname3-r"); + +done: + smb2_deltree(tree1, BASEDIR_REC); + return ret; +} + +/* + testing of change notify mask change +*/ + +#define BASEDIR_MC BASEDIR "_MC" + +static bool torture_smb2_notify_mask_change(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io, io1; + struct smb2_handle h1; + struct smb2_request *req1, *req2; + union smb_setfileinfo sinfo; + + smb2_deltree(tree1, BASEDIR_MC); + smb2_util_rmdir(tree1, BASEDIR_MC); + + torture_comment(torture, "TESTING CHANGE NOTIFY WITH MASK CHANGE\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_MC; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, on file or directory name + changes. Setup both with and without recursion */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_ATTRIBUTES; + notify.smb2.in.file.handle = h1; + + notify.smb2.in.recursive = true; + req1 = smb2_notify_send(tree1, &(notify.smb2)); + + smb2_cancel(req1); + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + + notify.smb2.in.recursive = false; + req2 = smb2_notify_send(tree1, &(notify.smb2)); + + smb2_cancel(req2); + status = smb2_notify_recv(req2, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + notify.smb2.in.recursive = true; + req1 = smb2_notify_send(tree1, &(notify.smb2)); + + /* Set to hidden then back again. */ + ZERO_STRUCT(io1.smb2); + io1.generic.level = RAW_OPEN_SMB2; + io1.smb2.in.create_flags = 0; + io1.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE| + SEC_RIGHTS_FILE_ALL; + io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io1.smb2.in.security_flags = 0; + io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io1.smb2.in.fname = BASEDIR_MC "\\tname1"; + + smb2_util_close(tree1, + custom_smb2_create(tree1, torture, &(io1.smb2))); + status = smb2_util_setatr(tree1, BASEDIR_MC "\\tname1", + FILE_ATTRIBUTE_HIDDEN); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_unlink(tree1, BASEDIR_MC "\\tname1"); + + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "tname1"); + + /* Now try and change the mask to include other events. + * This should not work - once the mask is set on a directory + * h1 it seems to be fixed until the fnum is closed. */ + + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_CREATION; + notify.smb2.in.recursive = true; + req1 = smb2_notify_send(tree1, &(notify.smb2)); + + notify.smb2.in.recursive = false; + req2 = smb2_notify_send(tree1, &(notify.smb2)); + + io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io1.smb2.in.fname = BASEDIR_MC "\\subdir-name"; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree2, io1.smb2.out.file.handle); + + ZERO_STRUCT(sinfo); + io1.smb2.in.fname = BASEDIR_MC "\\subdir-name\\subname1"; + io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR_MC "\\subdir-name\\subname1-r"; + status = smb2_setinfo_file(tree2, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + io1.smb2.in.fname = BASEDIR_MC "\\subdir-name\\subname2"; + io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle; + sinfo.rename_information.in.new_name = BASEDIR_MC "\\subname2-r"; + status = smb2_setinfo_file(tree2, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree2, io1.smb2.out.file.handle); + + io1.smb2.in.fname = BASEDIR_MC "\\subname2-r"; + io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, torture, &(io1.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle; + sinfo.rename_information.in.new_name = BASEDIR_MC "\\subname3-r"; + status = smb2_setinfo_file(tree2, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree2, io1.smb2.out.file.handle); + + status = smb2_util_rmdir(tree2, BASEDIR_MC "\\subdir-name\\subname1-r"); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_rmdir(tree2, BASEDIR_MC "\\subdir-name"); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_unlink(tree2, BASEDIR_MC "\\subname3-r"); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subname2-r"); + + status = smb2_notify_recv(req2, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subname3-r"); + + if (!ret) { + goto done; + } + +done: + smb2_deltree(tree1, BASEDIR_MC); + return ret; +} + +/* + testing of mask bits for change notify +*/ + +#define BASEDIR_MSK BASEDIR "_MSK" + +static bool torture_smb2_notify_mask(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io, io1; + struct smb2_handle h1, h2; + int i; + char c = 1; + union smb_setfileinfo sinfo; + + smb2_deltree(tree1, BASEDIR_MSK); + smb2_util_rmdir(tree1, BASEDIR_MSK); + + torture_comment(torture, "TESTING CHANGE NOTIFY COMPLETION FILTERS\n"); + + + ZERO_STRUCT(h1); + ZERO_STRUCT(h2); + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_MSK; + + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.recursive = true; + +#define NOTIFY_MASK_TEST(test_name, setup, op, cleanup, Action, \ + expected, nchanges) \ + do { \ + do { for (i=0;i<32;i++) { \ + struct smb2_request *req; \ + status = smb2_create(tree1, torture, &(io.smb2)); \ + CHECK_STATUS(status, NT_STATUS_OK); \ + h1 = io.smb2.out.file.handle; \ + setup \ + notify.smb2.in.file.handle = h1; \ + notify.smb2.in.completion_filter = ((uint32_t)1<<i); \ + /* cancel initial requests so the buffer is setup */ \ + req = smb2_notify_send(tree1, &(notify.smb2)); \ + smb2_cancel(req); \ + status = smb2_notify_recv(req, torture, &(notify.smb2)); \ + CHECK_STATUS(status, NT_STATUS_CANCELLED); \ + /* send the change notify request */ \ + req = smb2_notify_send(tree1, &(notify.smb2)); \ + op \ + smb_msleep(200); smb2_cancel(req); \ + status = smb2_notify_recv(req, torture, &(notify.smb2)); \ + cleanup \ + smb2_util_close(tree1, h1); \ + if (NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED)) continue; \ + CHECK_STATUS(status, NT_STATUS_OK); \ + /* special case to cope with file rename behaviour */ \ + if (nchanges == 2 && notify.smb2.out.num_changes == 1 && \ + notify.smb2.out.changes[0].action == \ + NOTIFY_ACTION_MODIFIED && \ + ((expected) & FILE_NOTIFY_CHANGE_ATTRIBUTES) && \ + Action == NOTIFY_ACTION_OLD_NAME) { \ + torture_comment(torture, \ + "(rename file special handling OK)\n"); \ + } else if (nchanges != notify.smb2.out.num_changes) { \ + torture_result(torture, TORTURE_FAIL, \ + "ERROR: nchanges=%d expected=%d "\ + "action=%d filter=0x%08x\n", \ + notify.smb2.out.num_changes, \ + nchanges, \ + notify.smb2.out.changes[0].action, \ + notify.smb2.in.completion_filter); \ + ret = false; \ + } else if (notify.smb2.out.changes[0].action != Action) { \ + torture_result(torture, TORTURE_FAIL, \ + "ERROR: nchanges=%d action=%d " \ + "expectedAction=%d filter=0x%08x\n", \ + notify.smb2.out.num_changes, \ + notify.smb2.out.changes[0].action, \ + Action, \ + notify.smb2.in.completion_filter); \ + ret = false; \ + } else if (strcmp(notify.smb2.out.changes[0].name.s, \ + "tname1") != 0) { \ + torture_result(torture, TORTURE_FAIL, \ + "ERROR: nchanges=%d action=%d " \ + "filter=0x%08x name=%s\n", \ + notify.smb2.out.num_changes, \ + notify.smb2.out.changes[0].action, \ + notify.smb2.in.completion_filter, \ + notify.smb2.out.changes[0].name.s); \ + ret = false; \ + } \ + } \ + } while (0); \ + } while (0); + + torture_comment(torture, "Testing mkdir\n"); + NOTIFY_MASK_TEST("Testing mkdir",;, + smb2_util_mkdir(tree2, BASEDIR_MSK "\\tname1");, + smb2_util_rmdir(tree2, BASEDIR_MSK "\\tname1");, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_DIR_NAME, 1); + + torture_comment(torture, "Testing create file\n"); + ZERO_STRUCT(io1.smb2); + io1.generic.level = RAW_OPEN_SMB2; + io1.smb2.in.create_flags = 0; + io1.smb2.in.desired_access = SEC_FILE_ALL; + io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io1.smb2.in.security_flags = 0; + io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io1.smb2.in.fname = BASEDIR_MSK "\\tname1"; + + NOTIFY_MASK_TEST("Testing create file",;, + smb2_util_close(tree2, custom_smb2_create(tree2, + torture, &(io1.smb2)));, + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_FILE_NAME, 1); + + torture_comment(torture, "Testing unlink\n"); + NOTIFY_MASK_TEST("Testing unlink", + smb2_util_close(tree2, custom_smb2_create(tree2, + torture, &(io1.smb2)));, + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");, + ;, + NOTIFY_ACTION_REMOVED, + FILE_NOTIFY_CHANGE_FILE_NAME, 1); + + torture_comment(torture, "Testing rmdir\n"); + NOTIFY_MASK_TEST("Testing rmdir", + smb2_util_mkdir(tree2, BASEDIR_MSK "\\tname1");, + smb2_util_rmdir(tree2, BASEDIR_MSK "\\tname1");, + ;, + NOTIFY_ACTION_REMOVED, + FILE_NOTIFY_CHANGE_DIR_NAME, 1); + + torture_comment(torture, "Testing rename file\n"); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = BASEDIR_MSK "\\tname2"; + NOTIFY_MASK_TEST("Testing rename file", + smb2_util_close(tree2, custom_smb2_create(tree2, + torture, &(io1.smb2)));, + smb2_setinfo_file(tree2, &sinfo);, + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname2");, + NOTIFY_ACTION_OLD_NAME, + FILE_NOTIFY_CHANGE_FILE_NAME, 2); + + torture_comment(torture, "Testing rename dir\n"); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = BASEDIR_MSK "\\tname2"; + NOTIFY_MASK_TEST("Testing rename dir", + smb2_util_mkdir(tree2, BASEDIR_MSK "\\tname1");, + smb2_setinfo_file(tree2, &sinfo);, + smb2_util_rmdir(tree2, BASEDIR_MSK "\\tname2");, + NOTIFY_ACTION_OLD_NAME, + FILE_NOTIFY_CHANGE_DIR_NAME, 2); + + torture_comment(torture, "Testing set path attribute\n"); + NOTIFY_MASK_TEST("Testing set path attribute", + smb2_util_close(tree2, custom_smb2_create(tree2, + torture, &(io.smb2)));, + smb2_util_setatr(tree2, BASEDIR_MSK "\\tname1", + FILE_ATTRIBUTE_HIDDEN);, + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, 1); + + torture_comment(torture, "Testing set path write time\n"); + ZERO_STRUCT(sinfo); + sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sinfo.generic.in.file.handle = h1; + sinfo.basic_info.in.write_time = 1000; + NOTIFY_MASK_TEST("Testing set path write time", + smb2_util_close(tree2, custom_smb2_create(tree2, + torture, &(io1.smb2)));, + smb2_setinfo_file(tree2, &sinfo);, + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_LAST_WRITE, 1); + + if (torture_setting_bool(torture, "samba3", false)) { + torture_comment(torture, + "Samba3 does not yet support create times " + "everywhere\n"); + } + else { + ZERO_STRUCT(sinfo); + sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sinfo.generic.in.file.handle = h1; + sinfo.basic_info.in.create_time = 0; + torture_comment(torture, "Testing set file create time\n"); + NOTIFY_MASK_TEST("Testing set file create time", + smb2_create_complex_file(torture, tree2, + BASEDIR_MSK "\\tname1", &h2);, + smb2_setinfo_file(tree2, &sinfo);, + (smb2_util_close(tree2, h2), + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_CREATION, 1); + } + + ZERO_STRUCT(sinfo); + sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sinfo.generic.in.file.handle = h1; + sinfo.basic_info.in.access_time = 0; + torture_comment(torture, "Testing set file access time\n"); + NOTIFY_MASK_TEST("Testing set file access time", + smb2_create_complex_file(torture, + tree2, + BASEDIR_MSK "\\tname1", + &h2);, + smb2_setinfo_file(tree2, &sinfo);, + (smb2_util_close(tree2, h2), + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_LAST_ACCESS, 1); + + ZERO_STRUCT(sinfo); + sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sinfo.generic.in.file.handle = h1; + sinfo.basic_info.in.change_time = 0; + torture_comment(torture, "Testing set file change time\n"); + NOTIFY_MASK_TEST("Testing set file change time", + smb2_create_complex_file(torture, + tree2, + BASEDIR_MSK "\\tname1", + &h2);, + smb2_setinfo_file(tree2, &sinfo);, + (smb2_util_close(tree2, h2), + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + 0, 1); + + + torture_comment(torture, "Testing write\n"); + NOTIFY_MASK_TEST("Testing write", + smb2_create_complex_file(torture, + tree2, + BASEDIR_MSK "\\tname1", + &h2);, + smb2_util_write(tree2, h2, &c, 10000, 1);, + (smb2_util_close(tree2, h2), + smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));, + NOTIFY_ACTION_MODIFIED, + 0, 1); + +done: + smb2_deltree(tree1, BASEDIR_MSK); + return ret; +} + +#define BASEDIR_FL BASEDIR "_FL" +/* + basic testing of change notify on files +*/ +static bool torture_smb2_notify_file(struct torture_context *torture, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_close cl; + union smb_notify notify; + struct smb2_request *req; + struct smb2_handle h1; + const char *fname = BASEDIR_FL "\\file.txt"; + + smb2_deltree(tree, BASEDIR_FL); + smb2_util_rmdir(tree, BASEDIR_FL); + + torture_comment(torture, "TESTING CHANGE NOTIFY ON FILES\n"); + status = torture_smb2_testdir(tree, BASEDIR_FL, &h1); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.file.handle = h1; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_STREAM_NAME; + notify.smb2.in.recursive = false; + + torture_comment(torture, + "Testing if notifies on file handles are invalid (should be)\n"); + + req = smb2_notify_send(tree, &(notify.smb2)); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + ZERO_STRUCT(cl.smb2); + cl.close.level = RAW_CLOSE_SMB2; + cl.close.in.file.handle = h1; + status = smb2_close(tree, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_deltree(tree, BASEDIR_FL); + return ret; +} +/* + basic testing of change notifies followed by a tdis +*/ + +#define BASEDIR_TD BASEDIR "_TD" + +static bool torture_smb2_notify_tree_disconnect( + struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + + smb2_deltree(tree, BASEDIR_TD); + smb2_util_rmdir(tree, BASEDIR_TD); + + torture_comment(torture, "TESTING CHANGE NOTIFY+CANCEL FOLLOWED BY " + "TREE-DISCONNECT\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_TD; + + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_cancel(req); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + + status = smb2_tdis(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + req = smb2_notify_send(tree, &(notify.smb2)); + + smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 0); + +done: + smb2_deltree(tree, BASEDIR_TD); + return ret; +} + +/* + testing of change notifies followed by a tdis - no cancel +*/ + +#define BASEDIR_NTDIS BASEDIR "_NTDIS" + +static bool torture_smb2_notify_tree_disconnect_1( + struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + + smb2_deltree(tree, BASEDIR_NTDIS); + smb2_util_rmdir(tree, BASEDIR_NTDIS); + + torture_comment(torture, "TESTING CHANGE NOTIFY ASYNC FOLLOWED BY " + "TREE-DISCONNECT\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_NTDIS; + + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree, &(notify.smb2)); + WAIT_FOR_ASYNC_RESPONSE(req); + + status = smb2_tdis(tree); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_NOTIFY_CLEANUP); + CHECK_VAL(notify.smb2.out.num_changes, 0); + +done: + smb2_deltree(tree, BASEDIR_NTDIS); + return ret; +} + +/* + basic testing of change notifies followed by a close +*/ + +#define BASEDIR_CNC BASEDIR "_CNC" + +static bool torture_smb2_notify_close(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + + smb2_deltree(tree1, BASEDIR_CNC); + smb2_util_rmdir(tree1, BASEDIR_CNC); + + torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY ULOGOFF\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_CNC; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree1, &(notify.smb2)); + + WAIT_FOR_ASYNC_RESPONSE(req); + + status = smb2_util_close(tree1, h1); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_NOTIFY_CLEANUP); + CHECK_VAL(notify.smb2.out.num_changes, 0); + +done: + smb2_deltree(tree1, BASEDIR_CNC); + return ret; +} + +/* + basic testing of change notifies followed by a ulogoff +*/ + +#define BASEDIR_NUL BASEDIR "_NUL" +static bool torture_smb2_notify_ulogoff(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + + smb2_deltree(tree1, BASEDIR_NUL); + smb2_util_rmdir(tree1, BASEDIR_NUL); + + torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY ULOGOFF\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_NUL; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree1, &(notify.smb2)); + + WAIT_FOR_ASYNC_RESPONSE(req); + + status = smb2_logoff(tree1->session); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_NOTIFY_CLEANUP); + CHECK_VAL(notify.smb2.out.num_changes, 0); + +done: + smb2_deltree(tree1, BASEDIR_NUL); + return ret; +} + +/* + basic testing of change notifies followed by a session reconnect +*/ + +#define BASEDIR_NSR BASEDIR "_NSR" + +static bool torture_smb2_notify_session_reconnect(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + uint64_t previous_session_id = 0; + struct smb2_session *session2 = NULL; + + smb2_deltree(tree1, BASEDIR_NSR); + smb2_util_rmdir(tree1, BASEDIR_NSR); + + torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY SESSION RECONNECT\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_NSR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree1, &(notify.smb2)); + + WAIT_FOR_ASYNC_RESPONSE(req); + + previous_session_id = smb2cli_session_current_id(tree1->session->smbXcli); + torture_assert(torture, torture_smb2_session_setup(torture, + tree1->session->transport, + previous_session_id, + torture, &session2), + "session setup with previous_session_id failed"); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_NOTIFY_CLEANUP); + CHECK_VAL(notify.smb2.out.num_changes, 0); + + status = smb2_logoff(tree1->session); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + + status = smb2_logoff(session2); + CHECK_STATUS(status, NT_STATUS_OK); +done: + smb2_deltree(tree1, BASEDIR_NSR); + return ret; +} + +/* + basic testing of change notifies followed by an invalid reauth +*/ + +#define BASEDIR_IR BASEDIR "_IR" + +static bool torture_smb2_notify_invalid_reauth(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + struct cli_credentials *invalid_creds; + + smb2_deltree(tree2, BASEDIR_IR); + smb2_util_rmdir(tree2, BASEDIR_IR); + + torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY invalid REAUTH\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_IR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree1, &(notify.smb2)); + + WAIT_FOR_ASYNC_RESPONSE(req); + + invalid_creds = cli_credentials_init(torture); + torture_assert(torture, (invalid_creds != NULL), "talloc error"); + cli_credentials_set_username(invalid_creds, "__none__invalid__none__", CRED_SPECIFIED); + cli_credentials_set_domain(invalid_creds, "__none__invalid__none__", CRED_SPECIFIED); + cli_credentials_set_password(invalid_creds, "__none__invalid__none__", CRED_SPECIFIED); + cli_credentials_set_realm(invalid_creds, NULL, CRED_SPECIFIED); + cli_credentials_set_workstation(invalid_creds, "", CRED_UNINITIALISED); + + status = smb2_session_setup_spnego(tree1->session, + invalid_creds, + 0 /* previous_session_id */); + CHECK_STATUS(status, NT_STATUS_LOGON_FAILURE); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_NOTIFY_CLEANUP); + CHECK_VAL(notify.smb2.out.num_changes, 0); + + /* + * Demonstrate that the session is no longer valid. + */ + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); +done: + smb2_deltree(tree2, BASEDIR_IR); + return ret; +} + +static void tcp_dis_handler(struct smb2_transport *t, void *p) +{ + struct smb2_tree *tree = (struct smb2_tree *)p; + smb2_transport_dead(tree->session->transport, + NT_STATUS_LOCAL_DISCONNECT); + t = NULL; + tree = NULL; +} + +/* + basic testing of change notifies followed by tcp disconnect +*/ + +#define BASEDIR_NTCPD BASEDIR "_NTCPD" + +static bool torture_smb2_notify_tcp_disconnect( + struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + + smb2_deltree(tree, BASEDIR_NTCPD); + smb2_util_rmdir(tree, BASEDIR_NTCPD); + + torture_comment(torture, + "TESTING CHANGE NOTIFY FOLLOWED BY TCP DISCONNECT\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_NTCPD; + + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_cancel(req); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + notify.smb2.in.recursive = true; + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_transport_idle_handler(tree->session->transport, + tcp_dis_handler, 250000, tree); + tree = NULL; + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_LOCAL_DISCONNECT); + +done: + return ret; +} + +/* + test setting up two change notify requests on one handle +*/ + +#define BASEDIR_NDOH BASEDIR "_NDOH" + +static bool torture_smb2_notify_double(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req1, *req2; + + smb2_deltree(tree1, BASEDIR_NDOH); + smb2_util_rmdir(tree1, BASEDIR_NDOH); + + torture_comment(torture, + "TESTING CHANGE NOTIFY TWICE ON ONE DIRECTORY\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ| + SEC_RIGHTS_FILE_WRITE| + SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_NDOH; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req1 = smb2_notify_send(tree1, &(notify.smb2)); + smb2_cancel(req1); + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + req2 = smb2_notify_send(tree1, &(notify.smb2)); + smb2_cancel(req2); + status = smb2_notify_recv(req2, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + smb2_util_mkdir(tree2, BASEDIR_NDOH "\\subdir-name"); + req1 = smb2_notify_send(tree1, &(notify.smb2)); + req2 = smb2_notify_send(tree1, &(notify.smb2)); + + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + smb2_util_mkdir(tree2, BASEDIR_NDOH "\\subdir-name2"); + + status = smb2_notify_recv(req2, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name2"); + +done: + smb2_deltree(tree1, BASEDIR_NDOH); + return ret; +} + + +/* + test multiple change notifies at different depths and with/without recursion +*/ + +#define BASEDIR_TREE BASEDIR "_TREE" + +static bool torture_smb2_notify_tree(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + union smb_notify notify; + union smb_open io; + struct smb2_request *req; + struct timeval tv; + struct { + const char *path; + bool recursive; + uint32_t filter; + int expected; + struct smb2_handle h1; + int counted; + } dirs[] = { + { + .path = BASEDIR_TREE "\\abc", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 30, + }, + { + .path = BASEDIR_TREE "\\zqy", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 8, + }, + { + .path = BASEDIR_TREE "\\atsy", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 4, + }, + { + .path = BASEDIR_TREE "\\abc\\foo", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 2, + }, + { + .path = BASEDIR_TREE "\\abc\\blah", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 13, + }, + { + .path = BASEDIR_TREE "\\abc\\blah", + .recursive = false, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 7, + }, + { + .path = BASEDIR_TREE "\\abc\\blah\\a", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 2, + }, + { + .path = BASEDIR_TREE "\\abc\\blah\\b", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 2, + }, + { + .path = BASEDIR_TREE "\\abc\\blah\\c", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 2, + }, + { + .path = BASEDIR_TREE "\\abc\\fooblah", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 2, + }, + { + .path = BASEDIR_TREE "\\zqy\\xx", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 2, + }, + { + .path = BASEDIR_TREE "\\zqy\\yyy", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 2, + }, + { + .path = BASEDIR_TREE "\\zqy\\..", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 40, + }, + { + .path = BASEDIR_TREE, + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 40, + }, + { + .path = BASEDIR_TREE, + .recursive = false, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 6, + }, + { + .path = BASEDIR_TREE "\\atsy", + .recursive = false, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 4, + }, + { + .path = BASEDIR_TREE "\\abc", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 24, + }, + { + .path = BASEDIR_TREE "\\abc", + .recursive = false, + .filter = FILE_NOTIFY_CHANGE_FILE_NAME, + .expected = 0, + }, + { + .path = BASEDIR_TREE "\\abc", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_FILE_NAME, + .expected = 0, + }, + { + .path = BASEDIR_TREE "\\abc", + .recursive = true, + .filter = FILE_NOTIFY_CHANGE_NAME, + .expected = 24, + }, + }; + int i; + NTSTATUS status; + bool all_done = false; + + smb2_deltree(tree, BASEDIR_TREE); + smb2_util_rmdir(tree, BASEDIR_TREE); + + torture_comment(torture, "TESTING NOTIFY FOR DIFFERENT DEPTHS\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_TREE; + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 20000; + + /* + setup the directory tree, and the notify buffer on each directory + */ + for (i=0;i<ARRAY_SIZE(dirs);i++) { + io.smb2.in.fname = dirs[i].path; + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + dirs[i].h1 = io.smb2.out.file.handle; + + notify.smb2.in.completion_filter = dirs[i].filter; + notify.smb2.in.file.handle = dirs[i].h1; + notify.smb2.in.recursive = dirs[i].recursive; + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_cancel(req); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + } + + /* trigger 2 events in each dir */ + for (i=0;i<ARRAY_SIZE(dirs);i++) { + char *path = talloc_asprintf(torture, "%s\\test.dir", + dirs[i].path); + smb2_util_mkdir(tree, path); + smb2_util_rmdir(tree, path); + talloc_free(path); + } + + /* give a bit of time for the events to propagate */ + tv = timeval_current(); + + do { + /* count events that have happened in each dir */ + for (i=0;i<ARRAY_SIZE(dirs);i++) { + notify.smb2.in.completion_filter = dirs[i].filter; + notify.smb2.in.file.handle = dirs[i].h1; + notify.smb2.in.recursive = dirs[i].recursive; + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_cancel(req); + notify.smb2.out.num_changes = 0; + status = smb2_notify_recv(req, torture, + &(notify.smb2)); + dirs[i].counted += notify.smb2.out.num_changes; + } + + all_done = true; + + for (i=0;i<ARRAY_SIZE(dirs);i++) { + if (dirs[i].counted != dirs[i].expected) { + all_done = false; + } + } + } while (!all_done && timeval_elapsed(&tv) < 20); + + torture_comment(torture, "took %.4f seconds to propagate all events\n", + timeval_elapsed(&tv)); + + for (i=0;i<ARRAY_SIZE(dirs);i++) { + if (dirs[i].counted != dirs[i].expected) { + torture_comment(torture, + "ERROR: i=%d expected %d got %d for '%s'\n", + i, dirs[i].expected, dirs[i].counted, + dirs[i].path); + ret = false; + } + } + + /* + run from the back, closing and deleting + */ + for (i=ARRAY_SIZE(dirs)-1;i>=0;i--) { + smb2_util_close(tree, dirs[i].h1); + smb2_util_rmdir(tree, dirs[i].path); + } + +done: + smb2_deltree(tree, BASEDIR_TREE); + smb2_util_rmdir(tree, BASEDIR_TREE); + return ret; +} + +/* + Test response when cached server events exceed single NT NOTFIY response + packet size. +*/ + +#define BASEDIR_OVF BASEDIR "_OVF" + +static bool torture_smb2_notify_overflow(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1, h2; + int count = 100; + struct smb2_request *req1; + int i; + + smb2_deltree(tree, BASEDIR_OVF); + smb2_util_rmdir(tree, BASEDIR_OVF); + + torture_comment(torture, "TESTING CHANGE NOTIFY EVENT OVERFLOW\n"); + + /* get a handle on the directory */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_OVF; + + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, on name changes. */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_NTTRANS; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + + notify.smb2.in.recursive = true; + req1 = smb2_notify_send(tree, &(notify.smb2)); + + /* cancel initial requests so the buffer is setup */ + smb2_cancel(req1); + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + /* open a lot of files, filling up the server side notify buffer */ + torture_comment(torture, + "Testing overflowed buffer notify on create of %d files\n", + count); + + for (i=0;i<count;i++) { + char *fname = talloc_asprintf(torture, + BASEDIR_OVF "\\test%d.txt", i); + union smb_open io1; + ZERO_STRUCT(io1.smb2); + io1.generic.level = RAW_OPEN_SMB2; + io1.smb2.in.create_flags = 0; + io1.smb2.in.desired_access = SEC_FILE_ALL; + io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io1.smb2.in.alloc_size = 0; + io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io1.smb2.in.security_flags = 0; + io1.smb2.in.fname = fname; + + h2 = custom_smb2_create(tree, torture, &(io1.smb2)); + talloc_free(fname); + smb2_util_close(tree, h2); + } + + req1 = smb2_notify_send(tree, &(notify.smb2)); + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_NOTIFY_ENUM_DIR); + CHECK_VAL(notify.smb2.out.num_changes, 0); + +done: + smb2_deltree(tree, BASEDIR_OVF); + return ret; +} + +/* + Test if notifications are returned for changes to the base directory. + They shouldn't be. +*/ + +#define BASEDIR_BAS BASEDIR "_BAS" + +static bool torture_smb2_notify_basedir(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req1; + + smb2_deltree(tree1, BASEDIR_BAS); + smb2_util_rmdir(tree1, BASEDIR_BAS); + + torture_comment(torture, "TESTING CHANGE NOTIFY BASEDIR EVENTS\n"); + + /* get a handle on the directory */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_BAS; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* create a test file that will also be modified */ + io.smb2.in.fname = BASEDIR_BAS "\\tname1"; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + status = smb2_create(tree2, torture, &(io.smb2)); + CHECK_STATUS(status,NT_STATUS_OK); + smb2_util_close(tree2, io.smb2.out.file.handle); + + /* ask for a change notify, on attribute changes. */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_ATTRIBUTES; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req1 = smb2_notify_send(tree1, &(notify.smb2)); + + /* set attribute on the base dir */ + smb2_util_setatr(tree2, BASEDIR_BAS, FILE_ATTRIBUTE_HIDDEN); + + /* set attribute on a file to assure we receive a notification */ + smb2_util_setatr(tree2, BASEDIR_BAS "\\tname1", FILE_ATTRIBUTE_HIDDEN); + smb_msleep(200); + + /* check how many responses were given, expect only 1 for the file */ + status = smb2_notify_recv(req1, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "tname1"); + +done: + smb2_deltree(tree1, BASEDIR_BAS); + return ret; +} + +/* + very simple change notify test +*/ + +#define BASEDIR_TCON BASEDIR "_TCON" + +static bool torture_smb2_notify_tcon(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1 = {{0}}; + struct smb2_request *req = NULL; + struct smb2_tree *tree1 = NULL; + const char *fname = BASEDIR_TCON "\\subdir-name"; + + smb2_deltree(tree, BASEDIR_TCON); + smb2_util_rmdir(tree, BASEDIR_TCON); + + torture_comment(torture, "TESTING SIMPLE CHANGE NOTIFY\n"); + + /* + get a handle on the directory + */ + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL | + FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_TCON; + + status = smb2_create(tree, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + torture_comment(torture, "Testing notify mkdir\n"); + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_cancel(req); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + notify.smb2.in.recursive = true; + req = smb2_notify_send(tree, &(notify.smb2)); + status = smb2_util_mkdir(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, "Testing notify rmdir\n"); + req = smb2_notify_send(tree, &(notify.smb2)); + status = smb2_util_rmdir(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, "SIMPLE CHANGE NOTIFY OK\n"); + + torture_comment(torture, "TESTING WITH SECONDARY TCON\n"); + if (!torture_smb2_tree_connect(torture, tree->session, tree, &tree1)) { + torture_warning(torture, "couldn't reconnect to share, bailing\n"); + ret = false; + goto done; + } + + torture_comment(torture, "tid1=%d tid2=%d\n", + smb2cli_tcon_current_id(tree->smbXcli), + smb2cli_tcon_current_id(tree1->smbXcli)); + + torture_comment(torture, "Testing notify mkdir\n"); + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_util_mkdir(tree1, fname); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, "Testing notify rmdir\n"); + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_util_rmdir(tree, fname); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, "CHANGE NOTIFY WITH TCON OK\n"); + + torture_comment(torture, "Disconnecting secondary tree\n"); + status = smb2_tdis(tree1); + CHECK_STATUS(status, NT_STATUS_OK); + talloc_free(tree1); + + torture_comment(torture, "Testing notify mkdir\n"); + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_util_mkdir(tree, fname); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, "Testing notify rmdir\n"); + req = smb2_notify_send(tree, &(notify.smb2)); + smb2_util_rmdir(tree, fname); + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(notify.smb2.out.num_changes, 1); + CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED); + CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name"); + + torture_comment(torture, "CHANGE NOTIFY WITH TDIS OK\n"); +done: + smb2_util_close(tree, h1); + smb2_deltree(tree, BASEDIR_TCON); + + return ret; +} + +#define BASEDIR_RMD BASEDIR "_RMD" + +static bool torture_smb2_notify_rmdir(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2, + bool initial_delete_on_close) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify = {}; + union smb_setfileinfo sfinfo = {}; + union smb_open io = {}; + struct smb2_handle h = {}; + struct smb2_request *req; + + torture_comment(torture, "TESTING NOTIFY CANCEL FOR DELETED DIR\n"); + + smb2_deltree(tree1, BASEDIR_RMD); + smb2_util_rmdir(tree1, BASEDIR_RMD); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE ; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_RMD; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.smb2.out.file.handle; + + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h; + notify.smb2.in.recursive = false; + + io.smb2.in.desired_access |= SEC_STD_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + req = smb2_notify_send(tree1, &(notify.smb2)); + + if (initial_delete_on_close) { + status = smb2_util_rmdir(tree2, BASEDIR_RMD); + CHECK_STATUS(status, NT_STATUS_OK); + } else { + status = smb2_create(tree2, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.generic.in.file.handle = io.smb2.out.file.handle; + sfinfo.disposition_info.in.delete_on_close = 1; + status = smb2_setinfo_file(tree2, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree2, io.smb2.out.file.handle); + } + + status = smb2_notify_recv(req, torture, &(notify.smb2)); + CHECK_STATUS(status, NT_STATUS_DELETE_PENDING); + +done: + + smb2_util_close(tree1, h); + smb2_deltree(tree1, BASEDIR_RMD); + + return ret; +} + +static bool torture_smb2_notify_rmdir1(struct torture_context *torture, + struct smb2_tree *tree) +{ + return torture_smb2_notify_rmdir(torture, tree, tree, false); +} + +static bool torture_smb2_notify_rmdir2(struct torture_context *torture, + struct smb2_tree *tree) +{ + return torture_smb2_notify_rmdir(torture, tree, tree, true); +} + +static bool torture_smb2_notify_rmdir3(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return torture_smb2_notify_rmdir(torture, tree1, tree2, false); +} + +static bool torture_smb2_notify_rmdir4(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return torture_smb2_notify_rmdir(torture, tree1, tree2, true); +} + +static void notify_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct smb2_request *req = talloc_get_type_abort( + private_data, struct smb2_request); + + smb2_cancel(req); +} + +#define BASEDIR_INR BASEDIR "_INR" + +static bool torture_smb2_inotify_rename(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + NTSTATUS status; + struct smb2_notify notify; + struct notify_changes change1 = {0}; + struct notify_changes change2 = {0}; + struct smb2_create create; + union smb_setfileinfo sinfo; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_request *req; + struct tevent_timer *te = NULL; + bool ok = false; + + smb2_deltree(tree1, BASEDIR_INR); + + torture_comment(torture, "Testing change notify of a rename with inotify\n"); + + status = torture_smb2_testdir(tree1, BASEDIR_INR, &h1); + torture_assert_ntstatus_ok_goto(torture, status, ok, done, "torture_smb2_testdir failed"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE| + SEC_RIGHTS_FILE_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = BASEDIR_INR "\\subdir-name"; + + status = smb2_create(tree2, torture, &create); + torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_create failed\n"); + h2 = create.out.file.handle; + + ZERO_STRUCT(notify); + notify.level = RAW_NOTIFY_SMB2; + notify.in.buffer_size = 4096; + notify.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.in.file.handle = h1; + notify.in.recursive = true; + req = smb2_notify_send(tree1, ¬ify); + torture_assert_not_null_goto(torture, req, ok, done, "smb2_notify_send failed\n"); + + while (!NT_STATUS_EQUAL(req->status, NT_STATUS_PENDING)) { + if (tevent_loop_once(torture->ev) != 0) { + goto done; + } + } + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h2; + sinfo.rename_information.in.new_name = BASEDIR_INR "\\subdir-name-r"; + + status = smb2_setinfo_file(tree2, &sinfo); + torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_setinfo_file failed\n"); + + smb2_util_close(tree2, h2); + + te = tevent_add_timer(torture->ev, + tree1, + tevent_timeval_current_ofs(1, 0), + notify_timeout, + req); + torture_assert_not_null_goto(torture, te, ok, done, "tevent_add_timer failed\n"); + + status = smb2_notify_recv(req, torture, ¬ify); + torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_notify_recv failed\n"); + + torture_assert_goto(torture, notify.out.num_changes == 1 || notify.out.num_changes == 2, + ok, done, "bad notify\n"); + + change1 = notify.out.changes[0]; + if (notify.out.num_changes == 2) { + change2 = notify.out.changes[1]; + } else { + /* + * We may only get one event at a time, so check for the + * matching second event for the oldname/newname or + * removed/added pair. + */ + ZERO_STRUCT(notify); + notify.level = RAW_NOTIFY_SMB2; + notify.in.buffer_size = 4096; + notify.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.in.file.handle = h1; + notify.in.recursive = true; + req = smb2_notify_send(tree1, ¬ify); + torture_assert_not_null_goto(torture, req, ok, done, "smb2_notify_send failed\n"); + + status = smb2_notify_recv(req, torture, ¬ify); + torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_notify_recv failed\n"); + + torture_assert_goto(torture, notify.out.num_changes == 1, ok, done, + "bad notify\n"); + + change2 = notify.out.changes[0]; + } + + if ((change1.action != NOTIFY_ACTION_OLD_NAME) && + (change1.action != NOTIFY_ACTION_REMOVED)) + { + torture_fail_goto(torture, done, "bad change notification\n"); + } + torture_assert_str_equal_goto(torture, change1.name.s, "subdir-name", + ok, done, "bad change notification\n"); + + if ((change2.action != NOTIFY_ACTION_NEW_NAME) && + (change2.action != NOTIFY_ACTION_ADDED)) + { + torture_fail_goto(torture, done, "bad change notification\n"); + } + torture_assert_str_equal_goto(torture, change2.name.s, "subdir-name-r", + ok, done, "bad change notification\n"); + + ok = true; +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree1, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree2, h2); + } + + smb2_deltree(tree1, BASEDIR_INR); + return ok; +} + +/* + Test asking for a change notify on a handle without permissions. +*/ + +#define BASEDIR_HPERM BASEDIR "_HPERM" + +static bool torture_smb2_notify_handle_permissions( + struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1 = {{0}}; + struct smb2_request *req; + + smb2_deltree(tree, BASEDIR_HPERM); + smb2_util_rmdir(tree, BASEDIR_HPERM); + + torture_comment(torture, + "TESTING CHANGE NOTIFY " + "ON A HANDLE WITHOUT PERMISSIONS\n"); + + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR_HPERM; + + status = smb2_create(tree, torture, &io.smb2); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree, ¬ify.smb2); + torture_assert_goto(torture, + req != NULL, + ret, + done, + "smb2_notify_send failed\n"); + + /* + * Cancel it, we don't really want to wait. + */ + smb2_cancel(req); + status = smb2_notify_recv(req, torture, ¬ify.smb2); + /* Handle h1 doesn't have permissions for ChangeNotify. */ + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR_HPERM); + return ret; +} + +/* + basic testing of SMB2 change notify +*/ +struct torture_suite *torture_smb2_notify_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "notify"); + + torture_suite_add_1smb2_test(suite, "valid-req", test_valid_request); + torture_suite_add_1smb2_test(suite, "tcon", torture_smb2_notify_tcon); + torture_suite_add_2smb2_test(suite, "dir", torture_smb2_notify_dir); + torture_suite_add_2smb2_test(suite, "mask", torture_smb2_notify_mask); + torture_suite_add_1smb2_test(suite, "tdis", torture_smb2_notify_tree_disconnect); + torture_suite_add_1smb2_test(suite, "tdis1", torture_smb2_notify_tree_disconnect_1); + torture_suite_add_2smb2_test(suite, "mask-change", torture_smb2_notify_mask_change); + torture_suite_add_1smb2_test(suite, "close", torture_smb2_notify_close); + torture_suite_add_1smb2_test(suite, "logoff", torture_smb2_notify_ulogoff); + torture_suite_add_1smb2_test(suite, "session-reconnect", torture_smb2_notify_session_reconnect); + torture_suite_add_2smb2_test(suite, "invalid-reauth", torture_smb2_notify_invalid_reauth); + torture_suite_add_1smb2_test(suite, "tree", torture_smb2_notify_tree); + torture_suite_add_2smb2_test(suite, "basedir", torture_smb2_notify_basedir); + torture_suite_add_2smb2_test(suite, "double", torture_smb2_notify_double); + torture_suite_add_1smb2_test(suite, "file", torture_smb2_notify_file); + torture_suite_add_1smb2_test(suite, "tcp", torture_smb2_notify_tcp_disconnect); + torture_suite_add_2smb2_test(suite, "rec", torture_smb2_notify_recursive); + torture_suite_add_1smb2_test(suite, "overflow", torture_smb2_notify_overflow); + torture_suite_add_1smb2_test(suite, "rmdir1", + torture_smb2_notify_rmdir1); + torture_suite_add_1smb2_test(suite, "rmdir2", + torture_smb2_notify_rmdir2); + torture_suite_add_2smb2_test(suite, "rmdir3", + torture_smb2_notify_rmdir3); + torture_suite_add_2smb2_test(suite, "rmdir4", + torture_smb2_notify_rmdir4); + torture_suite_add_1smb2_test(suite, + "handle-permissions", + torture_smb2_notify_handle_permissions); + + suite->description = talloc_strdup(suite, "SMB2-NOTIFY tests"); + + return suite; +} + +/* + basic testing of SMB2 change notify +*/ +struct torture_suite *torture_smb2_notify_inotify_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "notify-inotify"); + + suite->description = talloc_strdup(suite, "SMB2-NOTIFY tests that use inotify"); + + torture_suite_add_2smb2_test(suite, "inotify-rename", torture_smb2_inotify_rename); + + return suite; +} diff --git a/source4/torture/smb2/notify_disabled.c b/source4/torture/smb2/notify_disabled.c new file mode 100644 index 0000000..f38941c --- /dev/null +++ b/source4/torture/smb2/notify_disabled.c @@ -0,0 +1,120 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 notify test suite + + Copyright (C) Stefan Metzmacher 2006 + Copyright (C) Andrew Tridgell 2009 + Copyright (C) Ralph Boehme 2015 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "torture/util.h" + +#include "system/filesys.h" +#include "auth/credentials/credentials.h" +#include "lib/cmdline/cmdline.h" +#include "librpc/gen_ndr/security.h" + +#include "lib/events/events.h" + +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" + +#define BASEDIR "test_notify_disabled" + +/* + basic testing of change notify on directories +*/ +static bool torture_smb2_notify_disabled(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_notify notify; + union smb_open io; + struct smb2_handle h1; + struct smb2_request *req; + + torture_comment(torture, "TESTING CHANGE NOTIFY DISABLED\n"); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + /* + get a handle on the directory + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OK, + ret, done, "smb2_create"); + h1 = io.smb2.out.file.handle; + + ZERO_STRUCT(notify.smb2); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = h1; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree1, &(notify.smb2)); + status = smb2_notify_recv(req, torture, &(notify.smb2)); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_NOT_IMPLEMENTED, + ret, done, "smb2_notify_recv"); + + status = smb2_util_close(tree1, h1); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OK, + ret, done, "smb2_create"); + +done: + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* + basic testing of SMB2 change notify +*/ +struct torture_suite *torture_smb2_notify_disabled_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, + "change_notify_disabled"); + + torture_suite_add_1smb2_test(suite, "notfiy_disabled", torture_smb2_notify_disabled); + suite->description = talloc_strdup(suite, "SMB2-CHANGE-NOTIFY-DISABLED tests"); + + return suite; +} diff --git a/source4/torture/smb2/oplock.c b/source4/torture/smb2/oplock.c new file mode 100644 index 0000000..8f275ba --- /dev/null +++ b/source4/torture/smb2/oplock.c @@ -0,0 +1,5405 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 oplocks + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan Metzmacher 2008 + + 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/>. +*/ + +#include "includes.h" + +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb_composite/smb_composite.h" +#include "libcli/resolve/resolve.h" +#include "libcli/smb/smbXcli_base.h" + +#include "lib/cmdline/cmdline.h" +#include "lib/events/events.h" + +#include "param/param.h" +#include "system/filesys.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/smb2/block.h" + +#include "lib/util/sys_rw.h" +#include "libcli/security/security.h" + +#define CHECK_RANGE(v, min, max) do { \ + if ((v) < (min) || (v) > (max)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s " \ + "got %d - should be between %d and %d\n", \ + __location__, #v, (int)v, (int)min, (int)max); \ + ret = false; \ + }} while (0) + +#define CHECK_STRMATCH(v, correct) do { \ + if (!v || strstr((v),(correct)) == NULL) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s "\ + "got '%s' - should be '%s'\n", \ + __location__, #v, v?v:"NULL", correct); \ + ret = false; \ + }} while (0) + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s " \ + "got 0x%x - should be 0x%x\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +#define BASEDIR "oplock_test" + +static struct { + struct smb2_handle handle; + uint8_t level; + struct smb2_break br; + int count; + int failures; + NTSTATUS failure_status; +} break_info; + +static void torture_oplock_break_callback(struct smb2_request *req) +{ + NTSTATUS status; + struct smb2_break br; + + ZERO_STRUCT(br); + status = smb2_break_recv(req, &break_info.br); + if (!NT_STATUS_IS_OK(status)) { + break_info.failures++; + break_info.failure_status = status; + } + + return; +} + +/* A general oplock break notification handler. This should be used when a + * test expects to break from batch or exclusive to a lower level. */ +static bool torture_oplock_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_tree *tree = private_data; + const char *name; + struct smb2_request *req; + ZERO_STRUCT(break_info.br); + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + switch (level) { + case SMB2_OPLOCK_LEVEL_II: + name = "level II"; + break; + case SMB2_OPLOCK_LEVEL_NONE: + name = "none"; + break; + default: + name = "unknown"; + break_info.failures++; + } + printf("Acking to %s [0x%02X] in oplock handler\n", name, level); + + break_info.br.in.file.handle = *handle; + break_info.br.in.oplock_level = level; + break_info.br.in.reserved = 0; + break_info.br.in.reserved2 = 0; + + req = smb2_break_send(tree, &break_info.br); + req->async.fn = torture_oplock_break_callback; + req->async.private_data = NULL; + return true; +} + +/* + A handler function for oplock break notifications. Send a break to none + request. +*/ +static bool torture_oplock_handler_ack_to_none(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_tree *tree = private_data; + struct smb2_request *req; + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + printf("Acking to none in oplock handler\n"); + + ZERO_STRUCT(break_info.br); + break_info.br.in.file.handle = *handle; + break_info.br.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + break_info.br.in.reserved = 0; + break_info.br.in.reserved2 = 0; + + req = smb2_break_send(tree, &break_info.br); + req->async.fn = torture_oplock_break_callback; + req->async.private_data = NULL; + + return true; +} + +/* + A handler function for oplock break notifications. Break from level II to + none. SMB2 requires that the client does not send an oplock break request to + the server in this case. +*/ +static bool torture_oplock_handler_level2_to_none( + struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + printf("Break from level II to none in oplock handler\n"); + + return true; +} + +/* A handler function for oplock break notifications. This should be used when + * test expects two break notifications, first to level II, then to none. */ +static bool torture_oplock_handler_two_notifications( + struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_tree *tree = private_data; + const char *name; + struct smb2_request *req; + ZERO_STRUCT(break_info.br); + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + switch (level) { + case SMB2_OPLOCK_LEVEL_II: + name = "level II"; + break; + case SMB2_OPLOCK_LEVEL_NONE: + name = "none"; + break; + default: + name = "unknown"; + break_info.failures++; + } + printf("Breaking to %s [0x%02X] in oplock handler\n", name, level); + + if (level == SMB2_OPLOCK_LEVEL_NONE) + return true; + + break_info.br.in.file.handle = *handle; + break_info.br.in.oplock_level = level; + break_info.br.in.reserved = 0; + break_info.br.in.reserved2 = 0; + + req = smb2_break_send(tree, &break_info.br); + req->async.fn = torture_oplock_break_callback; + req->async.private_data = NULL; + return true; +} +static void torture_oplock_handler_close_recv(struct smb2_request *req) +{ + if (!smb2_request_receive(req)) { + printf("close failed in oplock_handler_close\n"); + break_info.failures++; + } +} + +/* + a handler function for oplock break requests - close the file +*/ +static bool torture_oplock_handler_close(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_close io; + struct smb2_tree *tree = private_data; + struct smb2_request *req; + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + ZERO_STRUCT(io); + io.in.file.handle = *handle; + io.in.flags = RAW_CLOSE_SMB2; + req = smb2_close_send(tree, &io); + if (req == NULL) { + printf("failed to send close in oplock_handler_close\n"); + return false; + } + + req->async.fn = torture_oplock_handler_close_recv; + req->async.private_data = NULL; + + return true; +} + +/* + a handler function for oplock break requests. Let it timeout +*/ +static bool torture_oplock_handler_timeout(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + printf("Let oplock break timeout\n"); + return true; +} + +static bool open_smb2_connection_no_level2_oplocks(struct torture_context *tctx, + struct smb2_tree **tree) +{ + NTSTATUS status; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct smbcli_options options; + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + options.use_level2_oplocks = false; + + status = smb2_connect(tctx, host, + lpcfg_smb_ports(tctx->lp_ctx), share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + tree, tctx->ev, &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to connect to SMB2 share " + "\\\\%s\\%s - %s\n", host, share, + nt_errstr(status)); + return false; + } + return true; +} + +static bool test_smb2_oplock_exclusive1(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_exclusive1.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h1; + struct smb2_handle h; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE1: open a file with an exclusive " + "oplock (share mode: none)\n"); + ZERO_STRUCT(break_info); + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + torture_comment(tctx, "a 2nd open should not cause a break\n"); + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "unlink it - should also be no break\n"); + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_exclusive2(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_exclusive2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE2: open a file with an exclusive " + "oplock (share mode: all)\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + torture_comment(tctx, "a 2nd open should cause a break to level 2\n"); + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h2 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + ZERO_STRUCT(break_info); + + /* now we have 2 level II oplocks... */ + torture_comment(tctx, "try to unlink it - should cause a break\n"); + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_ok(tctx, status, "Error unlinking the file"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "close both handles\n"); + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_exclusive3(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_exclusive3.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_setfileinfo sfi; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE3: open a file with an exclusive " + "oplock (share mode: none)\n"); + + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + torture_comment(tctx, "setpathinfo EOF should trigger a break to " + "none\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sfi.generic.in.file.path = fname; + sfi.end_of_file_info.in.size = 100; + + status = smb2_composite_setpathinfo(tree2, &sfi); + + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_NONE); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_exclusive4(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_exclusive4.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE4: open with exclusive oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + ZERO_STRUCT(break_info); + torture_comment(tctx, "second open with attributes only shouldn't " + "cause oplock break\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, NO_OPLOCK_RETURN); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_exclusive5(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_exclusive5.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "EXCLUSIVE5: open with exclusive oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and " + "NTCREATEX_DISP_OVERWRITE_IF dispostion causes " + "oplock break\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_II; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_exclusive6(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname1 = BASEDIR "\\test_exclusive6_1.dat"; + const char *fname2 = BASEDIR "\\test_exclusive6_2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_setfileinfo sinfo; + struct smb2_close closeio; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree2, fname2); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname1; + + torture_comment(tctx, "EXCLUSIVE6: open a file with an exclusive " + "oplock (share mode: none)\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + torture_comment(tctx, "rename with the parent directory handle open " + "for DELETE should not generate a break but get " + "a sharing violation\n"); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.new_name = fname2; + status = smb2_setinfo_file(tree1, &sinfo); + + torture_comment(tctx, "trying rename while parent handle open for delete.\n"); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + /* Close the parent directory handle. */ + ZERO_STRUCT(closeio); + closeio.in.file.handle = h; + status = smb2_close(tree1, &closeio); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_OK, + "Incorrect status"); + + /* Re-open without DELETE access. */ + ZERO_STRUCT(io); + io.smb2.in.oplock_level = 0; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL & (~SEC_STD_DELETE); + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the base directory"); + + torture_comment(tctx, "rename with the parent directory handle open " + "without DELETE should succeed without a break\n"); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.new_name = fname2; + status = smb2_setinfo_file(tree1, &sinfo); + + torture_comment(tctx, "trying rename while parent handle open without delete\n"); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_OK, + "Incorrect status"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_exclusive9(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_exclusive9.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h1, h2; + int i; + + struct { + uint32_t create_disposition; + uint32_t break_level; + } levels[] = { + { NTCREATEX_DISP_SUPERSEDE, SMB2_OPLOCK_LEVEL_NONE }, + { NTCREATEX_DISP_OPEN, SMB2_OPLOCK_LEVEL_II }, + { NTCREATEX_DISP_OVERWRITE_IF, SMB2_OPLOCK_LEVEL_NONE }, + { NTCREATEX_DISP_OPEN_IF, SMB2_OPLOCK_LEVEL_II }, + }; + + + status = torture_smb2_testdir(tree1, BASEDIR, &h1); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + smb2_util_close(tree1, h1); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, + "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, + SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_disposition = levels[i].create_disposition; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, + "Error opening the file"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, levels[i].break_level); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h1); + } + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch1(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch1.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1; + char c = 0; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "BATCH1: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "unlink should generate a break\n"); + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "2nd unlink should not generate a break\n"); + ZERO_STRUCT(break_info); + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + torture_comment(tctx, "writing should generate a self break to none\n"); + tree1->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + smb2_util_write(tree1, h1, &c, 0, 1); + + torture_wait_for_oplock_break(tctx); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_NONE); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch2(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + char c = 0; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH2: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "unlink should generate a break, which we ack " + "as break to none\n"); + tree1->session->transport->oplock.handler = + torture_oplock_handler_ack_to_none; + tree1->session->transport->oplock.private_data = tree1; + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + torture_comment(tctx, "2nd unlink should not generate a break\n"); + ZERO_STRUCT(break_info); + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + torture_comment(tctx, "writing should not generate a break\n"); + smb2_util_write(tree1, h1, &c, 0, 1); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch3(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch3.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH3: if we close on break then the unlink " + "can succeed\n"); + ZERO_STRUCT(break_info); + tree1->session->transport->oplock.handler = + torture_oplock_handler_close; + tree1->session->transport->oplock.private_data = tree1; + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch4(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch4.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_read r; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH4: a self read should not cause a break\n"); + ZERO_STRUCT(break_info); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(r); + r.in.file.handle = h1; + r.in.offset = 0; + + status = smb2_read(tree1, tree1, &r); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch5(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch5.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH5: a 2nd open should give a break\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch6(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch6.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + char c = 0; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH6: a 2nd open should give a break to " + "level II if the first open allowed shared read\n"); + ZERO_STRUCT(break_info); + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + ZERO_STRUCT(break_info); + + torture_comment(tctx, "write should trigger a break to none on both\n"); + tree1->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + tree2->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + smb2_util_write(tree1, h1, &c, 0, 1); + + /* We expect two breaks */ + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + + CHECK_VAL(break_info.count, 2); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch7(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch7.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH7: a 2nd open should get an oplock when " + "we close instead of ack\n"); + ZERO_STRUCT(break_info); + tree1->session->transport->oplock.handler = + torture_oplock_handler_close; + tree1->session->transport->oplock.private_data = tree1; + + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h2.data[0]); + CHECK_VAL(break_info.level, 1); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree2, h1); + smb2_util_close(tree2, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch8(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch8.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH8: open with batch oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + torture_comment(tctx, "second open with attributes only shouldn't " + "cause oplock break\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch9(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch9.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + char c = 0; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH9: open with attributes only can create " + "file\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "Subsequent normal open should break oplock on " + "attribute only open to level II\n"); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + smb2_util_close(tree2, h2); + + torture_comment(tctx, "third oplocked open should grant level2 without " + "break\n"); + ZERO_STRUCT(break_info); + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "write should trigger a break to none on both\n"); + tree1->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + tree2->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + smb2_util_write(tree2, h2, &c, 0, 1); + + /* We expect two breaks */ + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + + CHECK_VAL(break_info.count, 2); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch9a(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch9a.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2, h3; + char c = 0; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH9: open with attributes only can create " + "file\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error creating the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.create_action, FILE_WAS_CREATED); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "Subsequent attributes open should not break\n"); + + ZERO_STRUCT(break_info); + + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h3 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(io.smb2.out.create_action, FILE_WAS_OPENED); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + smb2_util_close(tree2, h3); + + torture_comment(tctx, "Subsequent normal open should break oplock on " + "attribute only open to level II\n"); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + smb2_util_close(tree2, h2); + + torture_comment(tctx, "third oplocked open should grant level2 without " + "break\n"); + ZERO_STRUCT(break_info); + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "write should trigger a break to none on both\n"); + tree1->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + tree2->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + smb2_util_write(tree2, h2, &c, 0, 1); + + /* We expect two breaks */ + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + + CHECK_VAL(break_info.count, 2); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + + +static bool test_smb2_oplock_batch10(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch10.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH10: Open with oplock after a non-oplock " + "open should grant level2\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.smb2.out.oplock_level, 0); + + tree2->session->transport->oplock.handler = + torture_oplock_handler_level2_to_none; + tree2->session->transport->oplock.private_data = tree2; + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_comment(tctx, "write should trigger a break to none\n"); + { + struct smb2_write wr; + DATA_BLOB data; + data = data_blob_talloc_zero(tree1, UINT16_MAX); + data.data[0] = (const uint8_t)'x'; + ZERO_STRUCT(wr); + wr.in.file.handle = h1; + wr.in.offset = 0; + wr.in.data = data; + status = smb2_write(tree1, &wr); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + } + + torture_wait_for_oplock_break(tctx); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h2.data[0]); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch11(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch11.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_setfileinfo sfi; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = + torture_oplock_handler_two_notifications; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* Test if a set-eof on pathname breaks an exclusive oplock. */ + torture_comment(tctx, "BATCH11: Test if setpathinfo set EOF breaks " + "oplocks.\n"); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h1 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sfi.generic.in.file.path = fname; + sfi.end_of_file_info.in.size = 100; + + status = smb2_composite_setpathinfo(tree2, &sfi); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + /* We expect two breaks */ + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + + CHECK_VAL(break_info.count, 2); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch12(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch12.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_setfileinfo sfi; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = + torture_oplock_handler_two_notifications; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* Test if a set-allocation size on pathname breaks an exclusive + * oplock. */ + torture_comment(tctx, "BATCH12: Test if setpathinfo allocation size " + "breaks oplocks.\n"); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h1 = io.smb2.out.file.handle; + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_ALLOCATION_INFORMATION; + sfi.generic.in.file.path = fname; + sfi.allocation_info.in.alloc_size = 65536 * 8; + + status = smb2_composite_setpathinfo(tree2, &sfi); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + /* We expect two breaks */ + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + + CHECK_VAL(break_info.count, 2); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch13(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch13.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH13: open with batch oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and " + "NTCREATEX_DISP_OVERWRITE dispostion causes " + "oplock break\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + + return ret; +} + +static bool test_smb2_oplock_batch14(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch14.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH14: open with batch oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and " + "NTCREATEX_DISP_SUPERSEDE dispostion causes " + "oplock break\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch15(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch15.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* Test if a qpathinfo all info on pathname breaks a batch oplock. */ + torture_comment(tctx, "BATCH15: Test if qpathinfo all info breaks " + "a batch oplock (should not).\n"); + + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + qfi.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree2, tctx, &qfi); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch16(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch16.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH16: open with batch oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "second open with attributes only and " + "NTCREATEX_DISP_OVERWRITE_IF dispostion causes " + "oplock break\n"); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_SYNCHRONIZE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* This function is a placeholder for the SMB1 RAW-OPLOCK-BATCH17 test. Since + * SMB2 doesn't have a RENAME command this test isn't applicable. However, + * it's much less confusing, when comparing test, to keep the SMB1 and SMB2 + * test numbers in sync. */ +#if 0 +static bool test_raw_oplock_batch17(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return true; +} +#endif + +/* This function is a placeholder for the SMB1 RAW-OPLOCK-BATCH18 test. Since + * SMB2 doesn't have an NTRENAME command this test isn't applicable. However, + * it's much less confusing, when comparing tests, to keep the SMB1 and SMB2 + * test numbers in sync. */ +#if 0 +static bool test_raw_oplock_batch18(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return true; +} +#endif + +static bool test_smb2_oplock_batch19(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *fname1 = BASEDIR "\\test_batch19_1.dat"; + const char *fname2 = BASEDIR "\\test_batch19_2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi; + union smb_setfileinfo sfi; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname1; + + torture_comment(tctx, "BATCH19: open a file with an batch oplock " + "(share mode: none)\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "setfileinfo rename info should not trigger " + "a break but should cause a sharing violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.generic.in.file.path = fname1; + sfi.rename_information.in.file.handle = h1; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.root_fid = 0; + sfi.rename_information.in.new_name = fname2; + + status = smb2_setinfo_file(tree1, &sfi); + + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + qfi.generic.in.file.handle = h1; + + status = smb2_getinfo_file(tree1, tctx, &qfi); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + CHECK_STRMATCH(qfi.all_info2.out.fname.s, fname1); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, fname1); + smb2_deltree(tree1, fname2); + return ret; +} + +static bool test_smb2_oplock_batch20(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname1 = BASEDIR "\\test_batch20_1.dat"; + const char *fname2 = BASEDIR "\\test_batch20_2.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + union smb_fileinfo qfi; + union smb_setfileinfo sfi; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname1; + + torture_comment(tctx, "BATCH20: open a file with an batch oplock " + "(share mode: all)\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "setfileinfo rename info should not trigger " + "a break but should cause a sharing violation\n"); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.rename_information.in.file.handle = h1; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.new_name = fname2; + + status = smb2_setinfo_file(tree1, &sfi); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + qfi.generic.in.file.handle = h1; + + status = smb2_getinfo_file(tree1, tctx, &qfi); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + CHECK_STRMATCH(qfi.all_info2.out.fname.s, fname1); + + torture_comment(tctx, "open the file a second time requesting batch " + "(share mode: all)\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.fname = fname1; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + + torture_comment(tctx, "setfileinfo rename info should not trigger " + "a break but should cause a sharing violation\n"); + ZERO_STRUCT(break_info); + ZERO_STRUCT(sfi); + sfi.generic.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfi.rename_information.in.file.handle = h2; + sfi.rename_information.in.overwrite = 0; + sfi.rename_information.in.new_name = fname2; + + status = smb2_setinfo_file(tree2, &sfi); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_SHARING_VIOLATION, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + qfi.generic.in.file.handle = h1; + + status = smb2_getinfo_file(tree1, tctx, &qfi); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + CHECK_STRMATCH(qfi.all_info2.out.fname.s, fname1); + + ZERO_STRUCT(qfi); + qfi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + qfi.generic.in.file.handle = h2; + + status = smb2_getinfo_file(tree2, tctx, &qfi); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + CHECK_STRMATCH(qfi.all_info2.out.fname.s, fname1); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, fname1); + return ret; +} + +static bool test_smb2_oplock_batch21(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *fname = BASEDIR "\\test_batch21.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1; + char c = 0; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "BATCH21: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "writing should not generate a break\n"); + status = smb2_util_write(tree1, h1, &c, 0, 1); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch22a(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *fname = BASEDIR "\\test_batch22a.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + struct timeval tv; + int timeout = torture_setting_int(tctx, "oplocktimeout", 35); + int te; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "BATCH22: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "a 2nd open should succeed after the oplock " + "break timeout\n"); + tv = timeval_current(); + tree1->session->transport->oplock.handler = + torture_oplock_handler_timeout; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_wait_for_oplock_break(tctx); + te = (int)timeval_elapsed(&tv); + CHECK_RANGE(te, timeout - 1, timeout + 15); + torture_comment(tctx, "waited %d seconds for oplock timeout\n", te); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h2); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch22b(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch22b.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2 = {{0}}; + struct timeval tv; + int timeout = torture_setting_int(tctx, "oplocktimeout", 35); + struct smb2_transport *transport1 = tree1->session->transport; + bool block_setup = false; + bool block_ok = false; + int te; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "BATCH22: open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "a 2nd open should succeed after the oplock " + "break timeout\n"); + tv = timeval_current(); + tree1->session->transport->oplock.handler = + torture_oplock_handler_timeout; + block_setup = test_setup_blocked_transports(tctx); + torture_assert(tctx, block_setup, "test_setup_blocked_transports"); + block_ok = test_block_smb2_transport(tctx, transport1); + torture_assert(tctx, block_ok, "test_block_smb2_transport"); + + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_wait_for_oplock_break(tctx); + te = (int)timeval_elapsed(&tv); + CHECK_RANGE(te, 0, timeout); + torture_comment(tctx, "waited %d seconds for oplock timeout\n", te); + + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + +done: + if (block_ok) { + test_unblock_smb2_transport(tctx, transport1); + } + test_cleanup_blocked_transports(tctx); + + smb2_util_close(tree1, h1); + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree1, h2); + } + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch23(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch23.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2, h3; + struct smb2_tree *tree3 = NULL; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + ret = open_smb2_connection_no_level2_oplocks(tctx, &tree3); + CHECK_VAL(ret, true); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + tree3->session->transport->oplock.handler = torture_oplock_handler; + tree3->session->transport->oplock.private_data = tree3; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH23: an open and ask for a batch oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a 2nd open without level2 oplock support " + "should generate a break to level2\n"); + status = smb2_create(tree3, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h3 = io.smb2.out.file.handle; + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a 3rd open with level2 oplock support should " + "not generate a break\n"); + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree3, h3); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch24(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch24.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2; + struct smb2_tree *tree3 = NULL; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + ret = open_smb2_connection_no_level2_oplocks(tctx, &tree3); + CHECK_VAL(ret, true); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + tree3->session->transport->oplock.handler = torture_oplock_handler; + tree3->session->transport->oplock.private_data = tree3; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH24: a open without level support and " + "ask for a batch oplock\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree3, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a 2nd open with level2 oplock support should " + "generate a break\n"); + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.handle.data[0], h2.data[0]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree3, h2); + smb2_util_close(tree2, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_batch25(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *fname = BASEDIR "\\test_batch25.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "BATCH25: open a file with an batch oplock " + "(share mode: none)\n"); + + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + status = smb2_util_setatr(tree1, fname, FILE_ATTRIBUTE_HIDDEN); + torture_assert_ntstatus_ok(tctx, status, "Setting attributes " + "shouldn't trigger an oplock break"); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, fname); + return ret; +} + +static bool test_smb2_oplock_batch26(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1, h2, h3; + const char *fname_base = BASEDIR "\\test_oplock.txt"; + const char *stream = "Stream One:$DATA"; + const char *fname_stream; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + fname_stream = talloc_asprintf(tctx, "%s:%s", fname_base, stream); + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = 0x120089; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname_base; + + /* + Open base file with a batch oplock. + */ + torture_comment(tctx, "Open the base file with batch oplock\n"); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening base file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "Got batch oplock on base file\n"); + + torture_comment(tctx, "Opening stream file with batch oplock..\n"); + + io.smb2.in.fname = fname_stream; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening stream file"); + h2 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "Got batch oplock on stream file\n"); + + torture_comment(tctx, "Open base file again with batch oplock\n"); + + io.smb2.in.fname = fname_base; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h3 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h2); + smb2_util_close(tree1, h3); + smb2_util_close(tree1, h); + smb2_deltree(tree1, BASEDIR); + return ret; + +} + +/* Test how oplocks work on streams. */ +static bool test_raw_oplock_stream1(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + NTSTATUS status; + union smb_open io; + const char *fname_base = BASEDIR "\\test_stream1.txt"; + const char *fname_stream, *fname_default_stream; + const char *default_stream = "::$DATA"; + const char *stream = "Stream One:$DATA"; + bool ret = true; + struct smb2_handle h, h_base, h_stream; + int i; + +#define NSTREAM_OPLOCK_RESULTS 8 + struct { + const char **fname; + bool open_base_file; + uint32_t oplock_req; + uint32_t oplock_granted; + } stream_oplock_results[NSTREAM_OPLOCK_RESULTS] = { + /* Request oplock on stream without the base file open. */ + {&fname_stream, false, SMB2_OPLOCK_LEVEL_BATCH, SMB2_OPLOCK_LEVEL_BATCH}, + {&fname_default_stream, false, SMB2_OPLOCK_LEVEL_BATCH, SMB2_OPLOCK_LEVEL_BATCH}, + {&fname_stream, false, SMB2_OPLOCK_LEVEL_EXCLUSIVE, SMB2_OPLOCK_LEVEL_EXCLUSIVE}, + {&fname_default_stream, false, SMB2_OPLOCK_LEVEL_EXCLUSIVE, SMB2_OPLOCK_LEVEL_EXCLUSIVE}, + + /* Request oplock on stream with the base file open. */ + {&fname_stream, true, SMB2_OPLOCK_LEVEL_BATCH, SMB2_OPLOCK_LEVEL_BATCH}, + {&fname_default_stream, true, SMB2_OPLOCK_LEVEL_BATCH, SMB2_OPLOCK_LEVEL_II}, + {&fname_stream, true, SMB2_OPLOCK_LEVEL_EXCLUSIVE, SMB2_OPLOCK_LEVEL_EXCLUSIVE}, + {&fname_default_stream, true, SMB2_OPLOCK_LEVEL_EXCLUSIVE, SMB2_OPLOCK_LEVEL_II}, + }; + + fname_stream = talloc_asprintf(tctx, "%s:%s", fname_base, stream); + fname_default_stream = talloc_asprintf(tctx, "%s%s", fname_base, + default_stream); + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* Initialize handles to "closed". Using -1 in the first 64-bytes + * as the sentry for this */ + h_stream.data[0] = -1; + + /* cleanup */ + smb2_util_unlink(tree1, fname_base); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + tree2->session->transport->oplock.handler = torture_oplock_handler; + tree2->session->transport->oplock.private_data = tree2; + + /* Setup generic open parameters. */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = (SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_STD_READ_CONTROL); + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + + /* Create the file with a stream */ + io.smb2.in.fname = fname_stream; + io.smb2.in.create_flags = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error creating file"); + smb2_util_close(tree1, io.smb2.out.file.handle); + + /* Change the disposition to open now that the file has been created. */ + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + + /* Try some permutations of taking oplocks on streams. */ + for (i = 0; i < NSTREAM_OPLOCK_RESULTS; i++) { + const char *fname = *stream_oplock_results[i].fname; + bool open_base_file = stream_oplock_results[i].open_base_file; + uint32_t oplock_req = stream_oplock_results[i].oplock_req; + uint32_t oplock_granted = + stream_oplock_results[i].oplock_granted; + + if (open_base_file) { + torture_comment(tctx, "Opening base file: %s with " + "%d\n", fname_base, SMB2_OPLOCK_LEVEL_BATCH); + io.smb2.in.fname = fname_base; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, + "Error opening file"); + CHECK_VAL(io.smb2.out.oplock_level, + SMB2_OPLOCK_LEVEL_BATCH); + h_base = io.smb2.out.file.handle; + } + + torture_comment(tctx, "%d: Opening stream: %s with %d\n", i, + fname, oplock_req); + io.smb2.in.fname = fname; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = oplock_req; + + /* Do the open with the desired oplock on the stream. */ + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening file"); + CHECK_VAL(io.smb2.out.oplock_level, oplock_granted); + smb2_util_close(tree1, io.smb2.out.file.handle); + + /* Cleanup the base file if it was opened. */ + if (open_base_file) + smb2_util_close(tree2, h_base); + } + + /* Open the stream with an exclusive oplock. */ + torture_comment(tctx, "Opening stream: %s with %d\n", + fname_stream, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + io.smb2.in.fname = fname_stream; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening file"); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + h_stream = io.smb2.out.file.handle; + + /* Open the base file and see if it contends. */ + ZERO_STRUCT(break_info); + torture_comment(tctx, "Opening base file: %s with %d\n", + fname_base, SMB2_OPLOCK_LEVEL_BATCH); + io.smb2.in.fname = fname_base; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening file"); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + smb2_util_close(tree2, io.smb2.out.file.handle); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + /* Open the stream again to see if it contends. */ + ZERO_STRUCT(break_info); + torture_comment(tctx, "Opening stream again: %s with " + "%d\n", fname_base, SMB2_OPLOCK_LEVEL_BATCH); + io.smb2.in.fname = fname_stream; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening file"); + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + smb2_util_close(tree2, io.smb2.out.file.handle); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, OPLOCK_BREAK_TO_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + + /* Close the stream. */ + if (h_stream.data[0] != -1) { + smb2_util_close(tree1, h_stream); + } + + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool test_smb2_oplock_doc(struct torture_context *tctx, struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_oplock_doc.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1; + union smb_setfileinfo sfinfo; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + smb2_util_close(tree, h); + + /* cleanup */ + smb2_util_unlink(tree, fname); + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "open a file with a batch oplock\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + torture_comment(tctx, "Set delete on close\n"); + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.generic.in.file.handle = h1; + sfinfo.disposition_info.in.delete_on_close = 1; + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + torture_comment(tctx, "2nd open should not break and get " + "DELETE_PENDING\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.create_options = 0; + io.smb2.in.desired_access = SEC_FILE_READ_DATA; + status = smb2_create(tree2, tctx, &io.smb2); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_DELETE_PENDING, + "Incorrect status"); + CHECK_VAL(break_info.count, 0); + + smb2_util_close(tree, h1); + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* Open a file with a batch oplock, then open it again from a second client + * requesting no oplock. Having two open file handles should break our own + * oplock during BRL acquisition. + */ +static bool test_smb2_oplock_brl1(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_batch_brl.dat"; + /*int fname, f;*/ + bool ret = true; + uint8_t buf[1000]; + union smb_open io; + NTSTATUS status; + struct smb2_lock lck; + struct smb2_lock_element lock[1]; + struct smb2_handle h, h1, h2; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = + torture_oplock_handler_two_notifications; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "open with batch oplock\n"); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* create a file with bogus data */ + memset(buf, 0, sizeof(buf)); + + status = smb2_util_write(tree1, h1,buf, 0, sizeof(buf)); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(tctx, "Failed to create file\n"); + ret = false; + goto done; + } + + torture_comment(tctx, "a 2nd open should give a break\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = 0; + status = smb2_create(tree2, tctx, &(io.smb2)); + h2 = io.smb2.out.file.handle; + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a self BRL acquisition should break to none\n"); + + ZERO_STRUCT(lock); + + lock[0].offset = 0; + lock[0].length = 4; + lock[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + ZERO_STRUCT(lck); + lck.in.file.handle = h1; + lck.in.locks = &lock[0]; + lck.in.lock_count = 1; + status = smb2_lock(tree1, &lck); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_NONE); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.failures, 0); + + /* expect no oplock break */ + ZERO_STRUCT(break_info); + lock[0].offset = 2; + status = smb2_lock(tree1, &lck); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_LOCK_NOT_GRANTED, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree2, h2); + smb2_util_close(tree1, h); + +done: + smb2_deltree(tree1, BASEDIR); + return ret; + +} + +/* Open a file with a batch oplock on one tree and then acquire a brl. + * We should not contend our own oplock. + */ +static bool test_smb2_oplock_brl2(struct torture_context *tctx, struct smb2_tree *tree1) +{ + const char *fname = BASEDIR "\\test_batch_brl.dat"; + /*int fname, f;*/ + bool ret = true; + uint8_t buf[1000]; + union smb_open io; + NTSTATUS status; + struct smb2_handle h, h1; + struct smb2_lock lck; + struct smb2_lock_element lock[1]; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "open with batch oplock\n"); + ZERO_STRUCT(break_info); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* create a file with bogus data */ + memset(buf, 0, sizeof(buf)); + + status = smb2_util_write(tree1, h1, buf, 0, sizeof(buf)); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(tctx, "Failed to create file\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a self BRL acquisition should not break to " + "none\n"); + + ZERO_STRUCT(lock); + + lock[0].offset = 0; + lock[0].length = 4; + lock[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + ZERO_STRUCT(lck); + lck.in.file.handle = h1; + lck.in.locks = &lock[0]; + lck.in.lock_count = 1; + status = smb2_lock(tree1, &lck); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + lock[0].offset = 2; + status = smb2_lock(tree1, &lck); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_LOCK_NOT_GRANTED, + "Incorrect status"); + + /* With one file handle open a BRL should not contend our oplock. + * Thus, no oplock break will be received and the entire break_info + * struct will be 0 */ + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + +done: + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* Open a file with a batch oplock twice from one tree and then acquire a + * brl. BRL acquisition should break our own oplock. + */ +static bool test_smb2_oplock_brl3(struct torture_context *tctx, struct smb2_tree *tree1) +{ + const char *fname = BASEDIR "\\test_batch_brl.dat"; + bool ret = true; + uint8_t buf[1000]; + union smb_open io; + NTSTATUS status; + struct smb2_handle h, h1, h2; + struct smb2_lock lck; + struct smb2_lock_element lock[1]; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + tree1->session->transport->oplock.handler = + torture_oplock_handler_two_notifications; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + with a batch oplock we get a break + */ + torture_comment(tctx, "open with batch oplock\n"); + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* create a file with bogus data */ + memset(buf, 0, sizeof(buf)); + status = smb2_util_write(tree1, h1, buf, 0, sizeof(buf)); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(tctx, "Failed to create file\n"); + ret = false; + goto done; + } + + torture_comment(tctx, "a 2nd open should give a break\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = 0; + status = smb2_create(tree1, tctx, &(io.smb2)); + h2 = io.smb2.out.file.handle; + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + CHECK_VAL(break_info.failures, 0); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "a self BRL acquisition should break to none\n"); + + ZERO_STRUCT(lock); + + lock[0].offset = 0; + lock[0].length = 4; + lock[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + ZERO_STRUCT(lck); + lck.in.file.handle = h1; + lck.in.locks = &lock[0]; + lck.in.lock_count = 1; + status = smb2_lock(tree1, &lck); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_NONE); + CHECK_VAL(break_info.handle.data[0], h1.data[0]); + CHECK_VAL(break_info.failures, 0); + + /* expect no oplock break */ + ZERO_STRUCT(break_info); + lock[0].offset = 2; + status = smb2_lock(tree1, &lck); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_LOCK_NOT_GRANTED, + "Incorrect status"); + + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h2); + smb2_util_close(tree1, h); + +done: + smb2_deltree(tree1, BASEDIR); + return ret; + +} + +/* Starting the SMB2 specific oplock tests at 500 so we can keep the SMB1 + * tests in sync with an identically numbered SMB2 test */ + +/* Test whether the server correctly returns an error when we send + * a response to a levelII to none oplock notification. */ +static bool test_smb2_oplock_levelII500(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + const char *fname = BASEDIR "\\test_levelII500.dat"; + NTSTATUS status; + bool ret = true; + union smb_open io; + struct smb2_handle h, h1; + char c = 0; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + tree1->session->transport->oplock.handler = torture_oplock_handler; + tree1->session->transport->oplock.private_data = tree1; + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment(tctx, "LEVELII500: acknowledging a break from II to " + "none should return an error\n"); + ZERO_STRUCT(break_info); + + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_II; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + h1 = io.smb2.out.file.handle; + CHECK_VAL(io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_II); + + ZERO_STRUCT(break_info); + + torture_comment(tctx, "write should trigger a break to none and when " + "we reply, an oplock break failure\n"); + smb2_util_write(tree1, h1, &c, 0, 1); + + /* Wait several times to receive both the break notification, and the + * NT_STATUS_INVALID_OPLOCK_PROTOCOL error in the break response */ + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + + /* There appears to be a race condition in W2K8 and W2K8R2 where + * sometimes the server will happily reply to our break response with + * NT_STATUS_OK, and sometimes it will return the OPLOCK_PROTOCOL + * error. As the MS-SMB2 doc states that a client should not reply to + * a level2 to none break notification, I'm leaving the protocol error + * as the expected behavior. */ + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 1); + torture_assert_ntstatus_equal(tctx, break_info.failure_status, + NT_STATUS_INVALID_OPLOCK_PROTOCOL, + "Incorrect status"); + + smb2_util_close(tree1, h1); + smb2_util_close(tree1, h); + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* + * Test a double-break. Open a file with exclusive. Send off a second open + * request with OPEN_IF, triggering a break to level2. This should respond + * with level2. Before replying to the break to level2, fire off a third open + * with OVERWRITE_IF. The expected sequence would be that the 3rd opener gets + * a level2 immediately triggered by a break to none, but that seems not the + * case. Still investigating what the right behaviour should be. + */ + +struct levelII501_state { + struct torture_context *tctx; + struct smb2_tree *tree1; + struct smb2_tree *tree2; + struct smb2_tree *tree3; + struct smb2_handle h; + struct smb2_handle h1; + union smb_open io; + + struct smb2_handle break_handle; + uint8_t break_to; + struct smb2_break br; + + bool done; +}; + +static bool torture_oplock_break_delay(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, void *private_data); +static void levelII501_break_done(struct smb2_request *req); +static void levelII501_open1_done(struct smb2_request *req); +static void levelII501_open2_done(struct smb2_request *req); +static void levelII501_2ndopen_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data); +static void levelII501_break_timeout_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data); +static void levelII501_timeout_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data); + +static bool test_smb2_oplock_levelII501(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = BASEDIR "\\test_levelII501.dat"; + NTSTATUS status; + bool ret = true; + struct levelII501_state *state; + struct smb2_request *req; + struct tevent_timer *te; + + state = talloc(tctx, struct levelII501_state); + state->tctx = tctx; + state->done = false; + state->tree1 = tree1; + state->tree2 = tree2; + + if (!torture_smb2_connection(tctx, &state->tree3)) { + torture_fail(tctx, "Establishing SMB2 connection failed\n"); + return false; + } + + status = torture_smb2_testdir(tree1, BASEDIR, &state->h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + /* + base ntcreatex parms + */ + ZERO_STRUCT(state->io.smb2); + state->io.generic.level = RAW_OPEN_SMB2; + state->io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + state->io.smb2.in.alloc_size = 0; + state->io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + state->io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + state->io.smb2.in.create_options = 0; + state->io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + state->io.smb2.in.security_flags = 0; + state->io.smb2.in.fname = fname; + + torture_comment(tctx, "LEVELII501: Test double break sequence\n"); + ZERO_STRUCT(break_info); + + state->io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + state->io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + state->io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + state->io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + tree1->session->transport->oplock.handler = torture_oplock_break_delay; + tree1->session->transport->oplock.private_data = state; + + status = smb2_create(tree1, tctx, &(state->io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + state->h1 = state->io.smb2.out.file.handle; + CHECK_VAL(state->io.smb2.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + /* + * Trigger a break to level2 + */ + + req = smb2_create_send(tree2, &state->io.smb2); + req->async.fn = levelII501_open1_done; + req->async.private_data = state; + + te = tevent_add_timer( + tctx->ev, tctx, tevent_timeval_current_ofs(0, 200000), + levelII501_2ndopen_cb, state); + torture_assert(tctx, te != NULL, "tevent_add_timer failed\n"); + + te = tevent_add_timer( + tctx->ev, tctx, tevent_timeval_current_ofs(2, 0), + levelII501_timeout_cb, state); + torture_assert(tctx, te != NULL, "tevent_add_timer failed\n"); + + while (!state->done) { + if (tevent_loop_once(tctx->ev) != 0) { + torture_comment(tctx, "tevent_loop_once failed\n"); + } + } + + return ret; +} + +/* + * Fire off a second open after a little timeout + */ + +static void levelII501_2ndopen_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct levelII501_state *state = talloc_get_type_abort( + private_data, struct levelII501_state); + struct smb2_request *req; + + state->io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + req = smb2_create_send(state->tree3, &state->io.smb2); + req->async.fn = levelII501_open2_done; + req->async.private_data = state; +} + +/* + * Postpone the break response by 500 msec + */ +static bool torture_oplock_break_delay(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, void *private_data) +{ + struct levelII501_state *state = talloc_get_type_abort( + private_data, struct levelII501_state); + const char *name; + struct tevent_timer *te; + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + state->break_handle = *handle; + state->break_to = level; + + switch(level) { + case SMB2_OPLOCK_LEVEL_II: + name = "level II"; + break; + case SMB2_OPLOCK_LEVEL_NONE: + name = "none"; + break; + default: + name = "unknown"; + break; + } + printf("Got break to %s [0x%02X] in oplock handler, postponing " + "break response for 500msec\n", name, level); + + te = tevent_add_timer( + state->tctx->ev, state->tctx, + tevent_timeval_current_ofs(0, 500000), + levelII501_break_timeout_cb, state); + torture_assert(state->tctx, te != NULL, "tevent_add_timer failed\n"); + + return true; +} + +static void levelII501_break_timeout_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct levelII501_state *state = talloc_get_type_abort( + private_data, struct levelII501_state); + struct smb2_request *req; + + talloc_free(te); + + ZERO_STRUCT(state->br); + state->br.in.file.handle = state->break_handle; + state->br.in.oplock_level = state->break_to; + + req = smb2_break_send(state->tree1, &state->br); + req->async.fn = levelII501_break_done; + req->async.private_data = state; +} + +static void levelII501_break_done(struct smb2_request *req) +{ + struct smb2_break io; + NTSTATUS status; + + status = smb2_break_recv(req, &io); + printf("break done: %s\n", nt_errstr(status)); +} + +static void levelII501_open1_done(struct smb2_request *req) +{ + struct levelII501_state *state = talloc_get_type_abort( + req->async.private_data, struct levelII501_state); + struct smb2_create io; + NTSTATUS status; + + status = smb2_create_recv(req, state, &io); + printf("open1 done: %s\n", nt_errstr(status)); +} + +static void levelII501_open2_done(struct smb2_request *req) +{ + struct levelII501_state *state = talloc_get_type_abort( + req->async.private_data, struct levelII501_state); + struct smb2_create io; + NTSTATUS status; + + status = smb2_create_recv(req, state, &io); + printf("open2 done: %s\n", nt_errstr(status)); +} + +static void levelII501_timeout_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct levelII501_state *state = talloc_get_type_abort( + private_data, struct levelII501_state); + talloc_free(te); + state->done = true; +} + +static bool test_smb2_oplock_levelII502(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) + +{ + const char *fname = BASEDIR "\\test_levelII502.dat"; + NTSTATUS status; + union smb_open io; + struct smb2_close closeio; + struct smb2_handle h; + + status = torture_smb2_testdir(tree1, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + /* cleanup */ + smb2_util_unlink(tree1, fname); + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + torture_comment( + tctx, + "LEVELII502: Open a stale LEVEL2 oplock with OVERWRITE"); + + io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_II; + status = smb2_create(tree1, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + torture_assert(tctx, + io.smb2.out.oplock_level==SMB2_OPLOCK_LEVEL_II, + "Did not get LEVEL_II oplock\n"); + + status = smbXcli_conn_samba_suicide( + tree1->session->transport->conn, 93); + torture_assert_ntstatus_ok(tctx, status, "suicide failed"); + + sleep(1); + + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + + status = smb2_create(tree2, tctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + torture_assert(tctx, + io.smb2.out.oplock_level==SMB2_OPLOCK_LEVEL_BATCH, + "Did not get BATCH oplock\n"); + + closeio = (struct smb2_close) { + .in.file.handle = io.smb2.out.file.handle, + }; + status = smb2_close(tree2, &closeio); + torture_assert_ntstatus_equal( + tctx, status, NT_STATUS_OK, "close failed"); + + return true; +} + +static bool test_oplock_statopen1_do(struct torture_context *tctx, + struct smb2_tree *tree, + uint32_t access_mask, + bool expect_stat_open) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTSTATUS status; + const char *fname = "oplock_statopen1.dat"; + bool ret = true; + + /* Open file with exclusive oplock. */ + cr = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = fname, + .in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH, + }; + status = smb2_create(tree, mem_ctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = cr.out.file.handle; + CHECK_VAL(cr.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + + /* Stat open */ + cr = (struct smb2_create) { + .in.desired_access = access_mask, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = fname, + }; + status = smb2_create(tree, mem_ctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h2 = cr.out.file.handle; + + if (expect_stat_open) { + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.level, 0); + CHECK_VAL(break_info.failures, 0); + if (!ret) { + goto done; + } + } else { + CHECK_VAL(break_info.count, 1); + } + +done: + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + talloc_free(mem_ctx); + return ret; +} + +static bool test_smb2_oplock_statopen1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "oplock_statopen1.dat"; + size_t i; + bool ret = true; + struct { + uint32_t access_mask; + bool expect_stat_open; + } tests[] = { + { + .access_mask = FILE_READ_DATA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_WRITE_DATA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_READ_EA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_WRITE_EA, + .expect_stat_open = false, + }, + { + .access_mask = FILE_EXECUTE, + .expect_stat_open = false, + }, + { + .access_mask = FILE_READ_ATTRIBUTES, + .expect_stat_open = true, + }, + { + .access_mask = FILE_WRITE_ATTRIBUTES, + .expect_stat_open = true, + }, + { + .access_mask = DELETE_ACCESS, + .expect_stat_open = false, + }, + { + .access_mask = READ_CONTROL_ACCESS, + .expect_stat_open = false, + }, + { + .access_mask = WRITE_DAC_ACCESS, + .expect_stat_open = false, + }, + { + .access_mask = WRITE_OWNER_ACCESS, + .expect_stat_open = false, + }, + { + .access_mask = SYNCHRONIZE_ACCESS, + .expect_stat_open = true, + }, + }; + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + ZERO_STRUCT(break_info); + + ret = test_oplock_statopen1_do(tctx, + tree, + tests[i].access_mask, + tests[i].expect_stat_open); + if (ret == true) { + continue; + } + torture_result(tctx, TORTURE_FAIL, + "test %zu: access_mask: %s, " + "expect_stat_open: %s\n", + i, + get_sec_mask_str(tree, tests[i].access_mask), + tests[i].expect_stat_open ? "yes" : "no"); + goto done; + } + +done: + smb2_util_unlink(tree, fname); + return ret; +} + +struct torture_suite *torture_smb2_oplocks_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "oplock"); + + torture_suite_add_2smb2_test(suite, "exclusive1", test_smb2_oplock_exclusive1); + torture_suite_add_2smb2_test(suite, "exclusive2", test_smb2_oplock_exclusive2); + torture_suite_add_2smb2_test(suite, "exclusive3", test_smb2_oplock_exclusive3); + torture_suite_add_2smb2_test(suite, "exclusive4", test_smb2_oplock_exclusive4); + torture_suite_add_2smb2_test(suite, "exclusive5", test_smb2_oplock_exclusive5); + torture_suite_add_2smb2_test(suite, "exclusive6", test_smb2_oplock_exclusive6); + torture_suite_add_2smb2_test(suite, "exclusive9", + test_smb2_oplock_exclusive9); + torture_suite_add_2smb2_test(suite, "batch1", test_smb2_oplock_batch1); + torture_suite_add_2smb2_test(suite, "batch2", test_smb2_oplock_batch2); + torture_suite_add_2smb2_test(suite, "batch3", test_smb2_oplock_batch3); + torture_suite_add_2smb2_test(suite, "batch4", test_smb2_oplock_batch4); + torture_suite_add_2smb2_test(suite, "batch5", test_smb2_oplock_batch5); + torture_suite_add_2smb2_test(suite, "batch6", test_smb2_oplock_batch6); + torture_suite_add_2smb2_test(suite, "batch7", test_smb2_oplock_batch7); + torture_suite_add_2smb2_test(suite, "batch8", test_smb2_oplock_batch8); + torture_suite_add_2smb2_test(suite, "batch9", test_smb2_oplock_batch9); + torture_suite_add_2smb2_test(suite, "batch9a", test_smb2_oplock_batch9a); + torture_suite_add_2smb2_test(suite, "batch10", test_smb2_oplock_batch10); + torture_suite_add_2smb2_test(suite, "batch11", test_smb2_oplock_batch11); + torture_suite_add_2smb2_test(suite, "batch12", test_smb2_oplock_batch12); + torture_suite_add_2smb2_test(suite, "batch13", test_smb2_oplock_batch13); + torture_suite_add_2smb2_test(suite, "batch14", test_smb2_oplock_batch14); + torture_suite_add_2smb2_test(suite, "batch15", test_smb2_oplock_batch15); + torture_suite_add_2smb2_test(suite, "batch16", test_smb2_oplock_batch16); + torture_suite_add_1smb2_test(suite, "batch19", test_smb2_oplock_batch19); + torture_suite_add_2smb2_test(suite, "batch20", test_smb2_oplock_batch20); + torture_suite_add_1smb2_test(suite, "batch21", test_smb2_oplock_batch21); + torture_suite_add_1smb2_test(suite, "batch22a", test_smb2_oplock_batch22a); + torture_suite_add_2smb2_test(suite, "batch22b", test_smb2_oplock_batch22b); + torture_suite_add_2smb2_test(suite, "batch23", test_smb2_oplock_batch23); + torture_suite_add_2smb2_test(suite, "batch24", test_smb2_oplock_batch24); + torture_suite_add_1smb2_test(suite, "batch25", test_smb2_oplock_batch25); + torture_suite_add_1smb2_test(suite, "batch26", test_smb2_oplock_batch26); + torture_suite_add_2smb2_test(suite, "stream1", test_raw_oplock_stream1); + torture_suite_add_2smb2_test(suite, "doc", test_smb2_oplock_doc); + torture_suite_add_2smb2_test(suite, "brl1", test_smb2_oplock_brl1); + torture_suite_add_1smb2_test(suite, "brl2", test_smb2_oplock_brl2); + torture_suite_add_1smb2_test(suite, "brl3", test_smb2_oplock_brl3); + torture_suite_add_1smb2_test(suite, "levelii500", test_smb2_oplock_levelII500); + torture_suite_add_2smb2_test(suite, "levelii501", + test_smb2_oplock_levelII501); + torture_suite_add_2smb2_test(suite, "levelii502", + test_smb2_oplock_levelII502); + torture_suite_add_1smb2_test(suite, "statopen1", test_smb2_oplock_statopen1); + suite->description = talloc_strdup(suite, "SMB2-OPLOCK tests"); + + return suite; +} + +/* + stress testing of oplocks +*/ +bool test_smb2_bench_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_tree **trees; + bool ret = true; + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + int torture_nprocs = torture_setting_int(tctx, "nprocs", 4); + int i, count=0; + int timelimit = torture_setting_int(tctx, "timelimit", 10); + union smb_open io; + struct timeval tv; + struct smb2_handle h; + + trees = talloc_array(mem_ctx, struct smb2_tree *, torture_nprocs); + + torture_comment(tctx, "Opening %d connections\n", torture_nprocs); + for (i=0;i<torture_nprocs;i++) { + if (!torture_smb2_connection(tctx, &trees[i])) { + return false; + } + talloc_steal(mem_ctx, trees[i]); + trees[i]->session->transport->oplock.handler = + torture_oplock_handler_close; + trees[i]->session->transport->oplock.private_data = trees[i]; + } + + status = torture_smb2_testdir(trees[0], BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + ZERO_STRUCT(io.smb2); + io.smb2.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\test.dat"; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + tv = timeval_current(); + + /* + we open the same file with SHARE_ACCESS_NONE from all the + connections in a round robin fashion. Each open causes an + oplock break on the previous connection, which is answered + by the oplock_handler_close() to close the file. + + This measures how fast we can pass on oplocks, and stresses + the oplock handling code + */ + torture_comment(tctx, "Running for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + for (i=0;i<torture_nprocs;i++) { + status = smb2_create(trees[i], mem_ctx, &(io.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Incorrect status"); + count++; + } + + if (torture_setting_bool(tctx, "progress", true)) { + torture_comment(tctx, "%.2f ops/second\r", + count/timeval_elapsed(&tv)); + } + } + + torture_comment(tctx, "%.2f ops/second\n", count/timeval_elapsed(&tv)); + smb2_util_close(trees[0], io.smb2.out.file.handle); + smb2_util_unlink(trees[0], BASEDIR "\\test.dat"); + smb2_deltree(trees[0], BASEDIR); + talloc_free(mem_ctx); + return ret; +} + +static struct hold_oplock_info { + const char *fname; + bool close_on_break; + uint32_t share_access; + struct smb2_handle handle; +} hold_info[] = { + { + .fname = BASEDIR "\\notshared_close", + .close_on_break = true, + .share_access = NTCREATEX_SHARE_ACCESS_NONE, + }, + { + .fname = BASEDIR "\\notshared_noclose", + .close_on_break = false, + .share_access = NTCREATEX_SHARE_ACCESS_NONE, + }, + { + .fname = BASEDIR "\\shared_close", + .close_on_break = true, + .share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, + }, + { + .fname = BASEDIR "\\shared_noclose", + .close_on_break = false, + .share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE, + }, +}; + +static bool torture_oplock_handler_hold(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, void *private_data) +{ + struct hold_oplock_info *info; + int i; + + for (i=0;i<ARRAY_SIZE(hold_info);i++) { + if (smb2_util_handle_equal(hold_info[i].handle, *handle)) + break; + } + + if (i == ARRAY_SIZE(hold_info)) { + printf("oplock break for unknown handle 0x%llx%llx\n", + (unsigned long long) handle->data[0], + (unsigned long long) handle->data[1]); + return false; + } + + info = &hold_info[i]; + + if (info->close_on_break) { + printf("oplock break on %s - closing\n", info->fname); + torture_oplock_handler_close(transport, handle, + level, private_data); + return true; + } + + printf("oplock break on %s - acking break\n", info->fname); + printf("Acking to none in oplock handler\n"); + + torture_oplock_handler_ack_to_none(transport, handle, + level, private_data); + return true; +} + +/* + used for manual testing of oplocks - especially interaction with + other filesystems (such as NFS and local access) +*/ +bool test_smb2_hold_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct torture_context *mem_ctx = talloc_new(tctx); + struct tevent_context *ev = tctx->ev; + int i; + struct smb2_handle h; + NTSTATUS status; + + torture_comment(tctx, "Setting up open files with oplocks in %s\n", + BASEDIR); + + status = torture_smb2_testdir(tree, BASEDIR, &h); + torture_assert_ntstatus_ok(tctx, status, "Error creating directory"); + + tree->session->transport->oplock.handler = torture_oplock_handler_hold; + tree->session->transport->oplock.private_data = tree; + + /* setup the files */ + for (i=0;i<ARRAY_SIZE(hold_info);i++) { + union smb_open io; + char c = 1; + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.alloc_size = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = hold_info[i].share_access; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.create_options = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = hold_info[i].fname; + io.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + io.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + torture_comment(tctx, "opening %s\n", hold_info[i].fname); + + status = smb2_create(tree, mem_ctx, &(io.smb2)); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to open %s - %s\n", + hold_info[i].fname, nt_errstr(status)); + return false; + } + + if (io.smb2.out.oplock_level != SMB2_OPLOCK_LEVEL_BATCH) { + torture_comment(tctx, "Oplock not granted for %s - " + "expected %d but got %d\n", + hold_info[i].fname, + SMB2_OPLOCK_LEVEL_BATCH, + io.smb2.out.oplock_level); + return false; + } + hold_info[i].handle = io.smb2.out.file.handle; + + /* make the file non-zero size */ + status = smb2_util_write(tree, hold_info[i].handle, &c, 0, 1); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(tctx, "Failed to write to file\n"); + return false; + } + } + + torture_comment(tctx, "Waiting for oplock events\n"); + tevent_loop_wait(ev); + smb2_deltree(tree, BASEDIR); + talloc_free(mem_ctx); + return true; +} + + +static bool test_smb2_kernel_oplocks1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "test_kernel_oplock1.dat"; + NTSTATUS status; + bool ret = true; + struct smb2_create create; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + + smb2_util_unlink(tree, fname); + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + ZERO_STRUCT(break_info); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_EXCLUSIVE\n"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done, + "Open didn't return NT_STATUS_SHARING_VIOLATION\n"); + h2 = create.out.file.handle; + + torture_wait_for_oplock_break(tctx); + if (break_info.count != 0) { + torture_warning(tctx, "Open caused oplock break\n"); + } + + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + return ret; +} + +static bool test_smb2_kernel_oplocks2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "test_kernel_oplock2.dat"; + const char *sname = "test_kernel_oplock2.dat:foo"; + NTSTATUS status; + bool ret = true; + struct smb2_create create; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + + smb2_util_unlink(tree, fname); + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + ZERO_STRUCT(break_info); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_EXCLUSIVE\n"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = sname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h2 = create.out.file.handle; + + torture_wait_for_oplock_break(tctx); + if (break_info.count != 0) { + torture_warning(tctx, "Stream open caused oplock break\n"); + } + + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + return ret; +} + +/** + * 1. 1st client opens file with oplock + * 2. 2nd client opens file + * + * Verify 2 triggers an oplock break + **/ +static bool test_smb2_kernel_oplocks3(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + const char *fname = "test_kernel_oplock3.dat"; + NTSTATUS status; + bool ret = true; + struct smb2_create create; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + + smb2_util_unlink(tree, fname); + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error creating testfile\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + ZERO_STRUCT(break_info); + + /* 1 */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_EXCLUSIVE\n"); + + /* 2 */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + + status = smb2_create(tree2, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h2 = create.out.file.handle; + + torture_wait_for_oplock_break(tctx); + torture_assert_goto(tctx, break_info.count == 1, ret, done, "Expected 1 oplock break\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + return ret; +} + +/** + * 1) create testfile with stream + * 2) open file r/w with batch oplock, sharing read/delete + * 3) open stream on file for reading + * + * Verify 3) doesn't trigger an oplock break + **/ +static bool test_smb2_kernel_oplocks4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "test_kernel_oplock4.dat"; + const char *sname = "test_kernel_oplock4.dat:foo"; + NTSTATUS status; + bool ret = true; + struct smb2_create create; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + ZERO_STRUCT(break_info); + smb2_util_unlink(tree, fname); + + /* 1 */ + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error creating testfile\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = sname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + /* 2 */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_BATCH, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_BATCH\n"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = sname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h2 = create.out.file.handle; + + torture_wait_for_oplock_break(tctx); + if (break_info.count != 0) { + torture_warning(tctx, "Stream open caused oplock break\n"); + } + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + return ret; +} + +/** + * 1) create testfile with stream + * 2) open stream r/w with batch oplock -> batch oplock granted + * 3) open stream r/o with batch oplock + * + * Verify 3) does trigger an oplock break + **/ +static bool test_smb2_kernel_oplocks5(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "test_kernel_oplock4.dat"; + const char *sname = "test_kernel_oplock4.dat:foo"; + NTSTATUS status; + bool ret = true; + struct smb2_create create; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + ZERO_STRUCT(break_info); + smb2_util_unlink(tree, fname); + + /* 1 */ + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error creating testfile\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = sname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + /* 2 */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = sname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_BATCH, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_BATCH\n"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = sname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h2 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_NONE, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_NONE\n"); + + torture_wait_for_oplock_break(tctx); + if (break_info.count != 1) { + torture_warning(tctx, "Stream open didn't cause oplock break\n"); + } + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + return ret; +} + +/** + * 1) create testfile with stream + * 2) 1st client opens stream r/w with batch oplock -> batch oplock granted + * 3) 2nd client opens stream r/o with batch oplock + * + * Verify 3) does trigger an oplock break + **/ +static bool test_smb2_kernel_oplocks6(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + const char *fname = "test_kernel_oplock6.dat"; + const char *sname = "test_kernel_oplock6.dat:foo"; + NTSTATUS status; + bool ret = true; + struct smb2_create create; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + + smb2_util_unlink(tree, fname); + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error creating testfile\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + ZERO_STRUCT(break_info); + + /* 1 */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = sname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + /* 2 */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h1 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_EXCLUSIVE\n"); + + /* 3 */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_READ; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + + status = smb2_create(tree2, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n"); + h2 = create.out.file.handle; + + torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_NONE, ret, done, + "Oplock level is not SMB2_OPLOCK_LEVEL_NONE\n"); + + torture_wait_for_oplock_break(tctx); + torture_assert_goto(tctx, break_info.count == 1, ret, done, "Expected 1 oplock break\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + return ret; +} + +/** + * Recreate regression test from bug: + * + * https://bugzilla.samba.org/show_bug.cgi?id=13058 + * + * 1. smbd-1 opens the file and sets the oplock + * 2. smbd-2 tries to open the file. open() fails(EAGAIN) and open is deferred. + * 3. smbd-1 sends oplock break request to the client. + * 4. smbd-1 closes the file. + * 5. smbd-1 opens the file and sets the oplock. + * 6. smbd-2 calls defer_open_done(), and should re-break the oplock. + **/ + +static bool test_smb2_kernel_oplocks7(struct torture_context *tctx, + struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + const char *fname = "test_kernel_oplock7.dat"; + NTSTATUS status; + bool ret = true; + struct smb2_create create; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + struct smb2_create create_2; + struct smb2_create io; + struct smb2_request *req; + + smb2_util_unlink(tree, fname); + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error creating testfile\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + /* Close the open file on break. */ + tree->session->transport->oplock.handler = torture_oplock_handler_close; + tree->session->transport->oplock.private_data = tree; + ZERO_STRUCT(break_info); + + /* 1 - open file with oplock */ + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.fname = fname; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error opening the file\n"); + CHECK_VAL(create.out.oplock_level, SMB2_OPLOCK_LEVEL_EXCLUSIVE); + + /* 2 - open file to break oplock */ + ZERO_STRUCT(create_2); + create_2.in.desired_access = SEC_RIGHTS_FILE_ALL; + create_2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create_2.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create_2.in.create_disposition = NTCREATEX_DISP_OPEN; + create_2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create_2.in.fname = fname; + create_2.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + /* Open on tree2 - should cause a break on tree */ + req = smb2_create_send(tree2, &create_2); + torture_assert(tctx, req != NULL, "smb2_create_send"); + + /* The oplock break handler should close the file. */ + /* Steps 3 & 4. */ + torture_wait_for_oplock_break(tctx); + + tree->session->transport->oplock.handler = torture_oplock_handler; + + /* + * 5 - re-open on tree. NB. There is a race here + * depending on which smbd goes first. We either get + * an oplock level of SMB2_OPLOCK_LEVEL_EXCLUSIVE if + * the close and re-open on tree is processed first, or + * SMB2_OPLOCK_LEVEL_NONE if the pending create on + * tree2 is processed first. + */ + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error opening the file\n"); + + h1 = create.out.file.handle; + if (create.out.oplock_level != SMB2_OPLOCK_LEVEL_EXCLUSIVE && + create.out.oplock_level != SMB2_OPLOCK_LEVEL_NONE) { + torture_result(tctx, + TORTURE_FAIL, + "(%s): wrong value for oplock got 0x%x\n", + __location__, + (unsigned int)create.out.oplock_level); + ret = false; + goto done; + + } + + /* 6 - retrieve the second open. */ + status = smb2_create_recv(req, tctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error opening the file\n"); + h2 = io.out.file.handle; + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree2, h2); + } + smb2_util_unlink(tree, fname); + return ret; +} + +#ifdef HAVE_KERNEL_OPLOCKS_LINUX + +#ifndef RT_SIGNAL_LEASE +#define RT_SIGNAL_LEASE (SIGRTMIN+1) +#endif + +static int got_break; + +/* + * Signal handler. + */ + +static void got_rt_break(int sig) +{ + got_break = 1; +} + +static int got_alarm; + +/* + * Signal handler. + */ + +static void got_alarm_fn(int sig) +{ + got_alarm = 1; +} + +/* + * Child process function. + */ + +static int do_child_process(int pipefd, const char *name) +{ + int ret = 0; + int fd = -1; + char c = 0; + struct sigaction act; + sigset_t set; + sigset_t empty_set; + + /* Block RT_SIGNAL_LEASE and SIGALRM. */ + sigemptyset(&set); + sigemptyset(&empty_set); + sigaddset(&set, RT_SIGNAL_LEASE); + sigaddset(&set, SIGALRM); + ret = sigprocmask(SIG_SETMASK, &set, NULL); + if (ret == -1) { + return 11; + } + + /* Set up a signal handler for RT_SIGNAL_LEASE. */ + ZERO_STRUCT(act); + act.sa_handler = got_rt_break; + ret = sigaction(RT_SIGNAL_LEASE, &act, NULL); + if (ret == -1) { + return 1; + } + /* Set up a signal handler for SIGALRM. */ + ZERO_STRUCT(act); + act.sa_handler = got_alarm_fn; + ret = sigaction(SIGALRM, &act, NULL); + if (ret == -1) { + return 1; + } + /* Open the passed in file and get a kernel oplock. */ + fd = open(name, O_RDWR, 0666); + if (fd == -1) { + return 2; + } + + ret = fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE); + if (ret == -1) { + close(fd); + return 3; + } + + ret = fcntl(fd, F_SETLEASE, F_WRLCK); + if (ret == -1) { + close(fd); + return 4; + } + + /* Tell the parent we're ready. */ + ret = sys_write(pipefd, &c, 1); + if (ret != 1) { + close(fd); + return 5; + } + + /* Ensure the pause doesn't hang forever. */ + alarm(5); + + /* Wait for RT_SIGNAL_LEASE or SIGALRM. */ + ret = sigsuspend(&empty_set); + if (ret != -1 || errno != EINTR) { + close(fd); + return 6; + } + + if (got_alarm == 1) { + close(fd); + return 10; + } + + if (got_break != 1) { + close(fd); + return 7; + } + + /* Cancel any pending alarm. */ + alarm(0); + + /* Force the server to wait for 3 seconds. */ + sleep(3); + + /* Remove our lease. */ + ret = fcntl(fd, F_SETLEASE, F_UNLCK); + if (ret == -1) { + close(fd); + return 8; + } + + ret = close(fd); + if (ret == -1) { + return 9; + } + + /* All is well. */ + return 0; +} + +static bool wait_for_child_oplock(struct torture_context *tctx, + const char *localdir, + const char *fname) +{ + int fds[2]; + int ret; + pid_t pid; + char *name = talloc_asprintf(tctx, + "%s/%s", + localdir, + fname); + + torture_assert(tctx, name != NULL, "talloc failed"); + + ret = pipe(fds); + torture_assert(tctx, ret != -1, "pipe failed"); + + pid = fork(); + torture_assert(tctx, pid != (pid_t)-1, "fork failed"); + + if (pid != (pid_t)0) { + char c; + /* Parent. */ + TALLOC_FREE(name); + close(fds[1]); + ret = sys_read(fds[0], &c, 1); + torture_assert(tctx, ret == 1, "read failed"); + return true; + } + + /* Child process. */ + close(fds[0]); + ret = do_child_process(fds[1], name); + _exit(ret); + /* Notreached. */ +} +#else +static bool wait_for_child_oplock(struct torture_context *tctx, + const char *localdir, + const char *fname) +{ + return false; +} +#endif + +static void child_sig_term_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + int *pstatus = (int *)private_data; + int status = 0; + + wait(&status); + if (WIFEXITED(status)) { + *pstatus = WEXITSTATUS(status); + } else { + *pstatus = status; + } +} + +/* + * Deal with a non-smbd process holding a kernel oplock. + */ + +static bool test_smb2_kernel_oplocks8(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "test_kernel_oplock8.dat"; + const char *fname1 = "tmp_test_kernel_oplock8.dat"; + NTSTATUS status; + bool ret = true; + struct smb2_create io; + struct smb2_request *req = NULL; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + const char *localdir = torture_setting_string(tctx, "localdir", NULL); + struct tevent_signal *se = NULL; + int child_exit_code = -1; + time_t start; + time_t end; + +#ifndef HAVE_KERNEL_OPLOCKS_LINUX + torture_skip(tctx, "Need kernel oplocks for test"); +#endif + + if (localdir == NULL) { + torture_skip(tctx, "Need localdir for test"); + } + + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname1); + status = torture_smb2_testfile(tree, fname, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error creating testfile\n"); + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + se = tevent_add_signal(tctx->ev, + tctx, + SIGCHLD, + 0, + child_sig_term_handler, + &child_exit_code); + torture_assert(tctx, se != NULL, "tevent_add_signal failed\n"); + + /* Take the oplock locally in a sub-process. */ + ret = wait_for_child_oplock(tctx, localdir, fname); + torture_assert_goto(tctx, ret, ret, done, + "Wait for child process failed.\n"); + + /* + * Now try and open. This should block for 3 seconds. + * while the child process is still alive. + */ + + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = fname; + + req = smb2_create_send(tree, &io); + torture_assert_goto(tctx, req != NULL, + ret, done, "smb2_create_send"); + + /* Ensure while the open is blocked the smbd is + still serving other requests. */ + io.in.fname = fname1; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + + /* Time the start -> end of the request. */ + start = time(NULL); + status = smb2_create(tree, tctx, &io); + end = time(NULL); + + /* Should succeed. */ + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error opening the second file\n"); + h1 = io.out.file.handle; + + /* in less than 2 seconds. Otherwise the server blocks. */ + torture_assert_goto(tctx, end - start < 2, + ret, done, "server was blocked !"); + + /* Pick up the return for the initial blocking open. */ + status = smb2_create_recv(req, tctx, &io); + + /* Which should also have succeeded. */ + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error opening the file\n"); + h2 = io.out.file.handle; + + /* Wait for the exit code from the child. */ + while (child_exit_code == -1) { + int rval = tevent_loop_once(tctx->ev); + torture_assert_goto(tctx, rval == 0, ret, + done, "tevent_loop_once error\n"); + } + + torture_assert_int_equal_goto(tctx, child_exit_code, 0, + ret, done, "Bad child exit code"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname1); + return ret; +} + +struct torture_suite *torture_smb2_kernel_oplocks_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "kernel-oplocks"); + + torture_suite_add_1smb2_test(suite, "kernel_oplocks1", test_smb2_kernel_oplocks1); + torture_suite_add_1smb2_test(suite, "kernel_oplocks2", test_smb2_kernel_oplocks2); + torture_suite_add_2smb2_test(suite, "kernel_oplocks3", test_smb2_kernel_oplocks3); + torture_suite_add_1smb2_test(suite, "kernel_oplocks4", test_smb2_kernel_oplocks4); + torture_suite_add_1smb2_test(suite, "kernel_oplocks5", test_smb2_kernel_oplocks5); + torture_suite_add_2smb2_test(suite, "kernel_oplocks6", test_smb2_kernel_oplocks6); + torture_suite_add_2smb2_test(suite, "kernel_oplocks7", test_smb2_kernel_oplocks7); + torture_suite_add_1smb2_test(suite, "kernel_oplocks8", test_smb2_kernel_oplocks8); + + suite->description = talloc_strdup(suite, "SMB2-KERNEL-OPLOCK tests"); + + return suite; +} diff --git a/source4/torture/smb2/oplock_break_handler.c b/source4/torture/smb2/oplock_break_handler.c new file mode 100644 index 0000000..c17d32a --- /dev/null +++ b/source4/torture/smb2/oplock_break_handler.c @@ -0,0 +1,169 @@ +/* + * Unix SMB/CIFS implementation. + * + * test suite for SMB2 replay + * + * Copyright (C) Anubhav Rakshit 2014 + * Copyright (C) Stefan Metzmacher 2014 + * + * 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/>. + */ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" +#include "oplock_break_handler.h" +#include "lib/events/events.h" + +struct break_info break_info; + +static void torture_oplock_ack_callback(struct smb2_request *req) +{ + NTSTATUS status; + + status = smb2_break_recv(req, &break_info.br); + if (!NT_STATUS_IS_OK(status)) { + break_info.failures++; + break_info.failure_status = status; + } +} + +/** + * A general oplock break notification handler. This should be used when a + * test expects to break from batch or exclusive to a lower level. + */ + +bool torture_oplock_ack_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_tree *tree = private_data; + const char *name; + struct smb2_request *req; + + ZERO_STRUCT(break_info.br); + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + switch (level) { + case SMB2_OPLOCK_LEVEL_II: + name = "level II"; + break; + case SMB2_OPLOCK_LEVEL_NONE: + name = "none"; + break; + default: + name = "unknown"; + break_info.failures++; + } + + break_info.br.in.file.handle = *handle; + break_info.br.in.oplock_level = level; + break_info.br.in.reserved = 0; + break_info.br.in.reserved2 = 0; + break_info.received_transport = tree->session->transport; + SMB_ASSERT(tree->session->transport == transport); + + if (break_info.oplock_skip_ack) { + torture_comment(break_info.tctx, + "transport[%p] skip acking to %s [0x%02X] in oplock handler\n", + transport, name, level); + return true; + } + + torture_comment(break_info.tctx, + "transport[%p] Acking to %s [0x%02X] in oplock handler\n", + transport, name, level); + + req = smb2_break_send(tree, &break_info.br); + req->async.fn = torture_oplock_ack_callback; + req->async.private_data = NULL; + + return true; +} + +/** + * A oplock break handler designed to ignore incoming break requests. + * This is used when incoming oplock break requests need to be ignored + */ +bool torture_oplock_ignore_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, void *private_data) +{ + return true; +} + +/* + * Timer handler function notifies the registering function that time is up + */ +static void timeout_cb(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + bool *timesup = (bool *)private_data; + *timesup = true; +} + +/* + * Wait a short period of time to receive a single oplock break request + */ +void torture_wait_for_oplock_break(struct torture_context *tctx) +{ + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + struct tevent_timer *te = NULL; + struct timeval ne; + bool timesup = false; + int old_count = break_info.count; + + /* Wait 1 second for an oplock break */ + ne = tevent_timeval_current_ofs(0, 1000000); + + te = tevent_add_timer(tctx->ev, tmp_ctx, ne, timeout_cb, ×up); + if (te == NULL) { + torture_comment(tctx, "Failed to wait for an oplock break. " + "test results may not be accurate.\n"); + goto done; + } + + torture_comment(tctx, "Waiting for a potential oplock break...\n"); + while (!timesup && break_info.count < old_count + 1) { + if (tevent_loop_once(tctx->ev) != 0) { + torture_comment(tctx, "Failed to wait for an oplock " + "break. test results may not be " + "accurate\n."); + goto done; + } + } + if (timesup) { + torture_comment(tctx, "... waiting for an oplock break timed out\n"); + } else { + torture_comment(tctx, "Got %u oplock breaks\n", + break_info.count - old_count); + } + +done: + /* We don't know if the timed event fired and was freed, we received + * our oplock break, or some other event triggered the loop. Thus, + * we create a tmp_ctx to be able to safely free/remove the timed + * event in all 3 cases. + */ + talloc_free(tmp_ctx); +} diff --git a/source4/torture/smb2/oplock_break_handler.h b/source4/torture/smb2/oplock_break_handler.h new file mode 100644 index 0000000..c576782 --- /dev/null +++ b/source4/torture/smb2/oplock_break_handler.h @@ -0,0 +1,57 @@ +/* + * Unix SMB/CIFS implementation. + * + * test suite for SMB2 replay + * + * Copyright (C) Anubhav Rakshit 2014 + * Copyright (C) Stefan Metzmacher 2014 + * + * 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/>. + */ + +#ifndef __OPLOCK_BREAK_HANDLER_H__ +#define __OPLOCK_BREAK_HANDLER_H__ + +struct break_info { + struct torture_context *tctx; + bool oplock_skip_ack; + struct smb2_handle handle; + uint8_t level; + struct smb2_break br; + int count; + int failures; + NTSTATUS failure_status; + struct smb2_transport *received_transport; +}; + +extern struct break_info break_info; + +bool torture_oplock_ack_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data); +bool torture_oplock_ignore_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data); +void torture_wait_for_oplock_break(struct torture_context *tctx); + +static inline void torture_reset_break_info(struct torture_context *tctx, + struct break_info *r) +{ + ZERO_STRUCTP(r); + r->tctx = tctx; +} + +#endif /* __OPLOCK_BREAK_HANDLER_H__ */ diff --git a/source4/torture/smb2/read.c b/source4/torture/smb2/read.c new file mode 100644 index 0000000..2bc0dc9 --- /dev/null +++ b/source4/torture/smb2/read.c @@ -0,0 +1,573 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 read test suite + + Copyright (C) Andrew Tridgell 2008 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include <tevent.h> + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" +#include "librpc/gen_ndr/ndr_ioctl.h" + + +#define CHECK_STATUS(_status, _expected) \ + torture_assert_ntstatus_equal_goto(torture, _status, _expected, \ + ret, done, "Incorrect status") + +#define CHECK_VALUE(v, correct) \ + torture_assert_int_equal_goto(torture, v, correct, \ + ret, done, "Incorrect value") + +#define FNAME "smb2_readtest.dat" +#define DNAME "smb2_readtest.dir" + +static bool test_read_eof(struct torture_context *torture, struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[64*1024]; + struct smb2_read rd; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + ZERO_STRUCT(buf); + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 5; + rd.in.offset = 0; + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 10; + rd.in.offset = 0; + rd.in.min_count = 1; + + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, 10); + + rd.in.min_count = 0; + rd.in.length = 10; + rd.in.offset = sizeof(buf); + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = sizeof(buf); + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, 0); + + rd.in.min_count = 1; + rd.in.length = 0; + rd.in.offset = sizeof(buf); + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + rd.in.min_count = 0; + rd.in.length = 2; + rd.in.offset = sizeof(buf) - 1; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, 1); + + rd.in.min_count = 2; + rd.in.length = 1; + rd.in.offset = sizeof(buf) - 1; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + rd.in.min_count = 0x10000; + rd.in.length = 1; + rd.in.offset = 0; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + rd.in.min_count = 0x10000 - 2; + rd.in.length = 1; + rd.in.offset = 0; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + rd.in.min_count = 10; + rd.in.length = 5; + rd.in.offset = 0; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + +done: + talloc_free(tmp_ctx); + return ret; +} + + +static bool test_read_position(struct torture_context *torture, struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[64*1024]; + struct smb2_read rd; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + union smb_fileinfo info; + + ZERO_STRUCT(buf); + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 10; + rd.in.offset = 0; + rd.in.min_count = 1; + + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, 10); + + info.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + info.generic.in.file.handle = h; + + status = smb2_getinfo_file(tree, tmp_ctx, &info); + CHECK_STATUS(status, NT_STATUS_OK); + if (torture_setting_bool(torture, "windows", false)) { + CHECK_VALUE(info.all_info2.out.position, 0); + } else { + CHECK_VALUE(info.all_info2.out.position, 10); + } + + +done: + talloc_free(tmp_ctx); + return ret; +} + +static bool test_read_dir(struct torture_context *torture, struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + struct smb2_read rd; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + status = torture_smb2_testdir(tree, DNAME, &h); + if (!NT_STATUS_IS_OK(status)) { + printf(__location__ " Unable to create test directory '%s' - %s\n", DNAME, nt_errstr(status)); + return false; + } + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 10; + rd.in.offset = 0; + rd.in.min_count = 1; + + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_DEVICE_REQUEST); + + rd.in.min_count = 11; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_DEVICE_REQUEST); + + rd.in.length = 0; + rd.in.min_count = 2592; + status = smb2_read(tree, tmp_ctx, &rd); + if (torture_setting_bool(torture, "windows", false)) { + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + } else { + CHECK_STATUS(status, NT_STATUS_INVALID_DEVICE_REQUEST); + } + + rd.in.length = 0; + rd.in.min_count = 0; + rd.in.channel = 0; + status = smb2_read(tree, tmp_ctx, &rd); + if (torture_setting_bool(torture, "windows", false)) { + CHECK_STATUS(status, NT_STATUS_OK); + } else { + CHECK_STATUS(status, NT_STATUS_INVALID_DEVICE_REQUEST); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static bool test_read_access(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[64 * 1024]; + struct smb2_read rd; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + ZERO_STRUCT(buf); + + /* create a file */ + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, h); + CHECK_STATUS(status, NT_STATUS_OK); + + /* open w/ READ access - success */ + status = torture_smb2_testfile_access( + tree, FNAME, &h, SEC_FILE_READ_ATTRIBUTE | SEC_FILE_READ_DATA); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 5; + rd.in.offset = 0; + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, h); + CHECK_STATUS(status, NT_STATUS_OK); + + /* open w/ EXECUTE access - success */ + status = torture_smb2_testfile_access( + tree, FNAME, &h, SEC_FILE_READ_ATTRIBUTE | SEC_FILE_EXECUTE); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 5; + rd.in.offset = 0; + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_close(tree, h); + CHECK_STATUS(status, NT_STATUS_OK); + + /* open without READ or EXECUTE access - access denied */ + status = torture_smb2_testfile_access(tree, FNAME, &h, + SEC_FILE_READ_ATTRIBUTE); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 5; + rd.in.offset = 0; + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + status = smb2_util_close(tree, h); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* + basic regression test for BUG 14607 + https://bugzilla.samba.org/show_bug.cgi?id=14607 +*/ +static bool test_read_bug14607(struct torture_context *torture, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[64 * 1024]; + struct smb2_read rd; + uint32_t timeout_msec; + DATA_BLOB out_input_buffer = data_blob_null; + DATA_BLOB out_output_buffer = data_blob_null; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + uint8_t *data = NULL; + uint32_t data_length = 0; + + memset(buf, 0x1f, ARRAY_SIZE(buf)); + + /* create a file */ + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = ARRAY_SIZE(buf); + rd.in.offset = 0; + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, ARRAY_SIZE(buf)); + torture_assert_mem_equal_goto(torture, rd.out.data.data, + buf, ARRAY_SIZE(buf), + ret, done, + "Invalid content smb2_read"); + + timeout_msec = tree->session->transport->options.request_timeout * 1000; + + status = smb2cli_read(tree->session->transport->conn, + timeout_msec, + tree->session->smbXcli, + tree->smbXcli, + rd.in.length, + rd.in.offset, + h.data[0], + h.data[1], + rd.in.min_count, + rd.in.remaining, + tmp_ctx, + &data, &data_length); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(data_length, ARRAY_SIZE(buf)); + torture_assert_mem_equal_goto(torture, data, + buf, ARRAY_SIZE(buf), + ret, done, + "Invalid content smb2cli_read"); + + status = smb2cli_ioctl(tree->session->transport->conn, + timeout_msec, + tree->session->smbXcli, + tree->smbXcli, + UINT64_MAX, /* in_fid_persistent */ + UINT64_MAX, /* in_fid_volatile */ + FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8, + 0, /* in_max_input_length */ + NULL, /* in_input_buffer */ + 1, /* in_max_output_length */ + NULL, /* in_output_buffer */ + SMB2_IOCTL_FLAG_IS_FSCTL, + tmp_ctx, + &out_input_buffer, + &out_output_buffer); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) || + NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) || + NT_STATUS_EQUAL(status, NT_STATUS_FS_DRIVER_REQUIRED) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST)) + { + torture_comment(torture, + "FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8: %s\n", + nt_errstr(status)); + torture_skip(torture, "server doesn't support FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8\n"); + } + torture_assert_ntstatus_ok(torture, status, "FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8"); + + torture_assert_int_equal(torture, out_output_buffer.length, 0, + "output length"); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = ARRAY_SIZE(buf); + rd.in.offset = 0; + status = smb2_read(tree, tree, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, ARRAY_SIZE(buf)); + torture_assert_mem_equal_goto(torture, rd.out.data.data, + buf, ARRAY_SIZE(buf), + ret, done, + "Invalid content after padding smb2_read"); + + status = smb2cli_read(tree->session->transport->conn, + timeout_msec, + tree->session->smbXcli, + tree->smbXcli, + rd.in.length, + rd.in.offset, + h.data[0], + h.data[1], + rd.in.min_count, + rd.in.remaining, + tmp_ctx, + &data, &data_length); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(data_length, ARRAY_SIZE(buf)); + torture_assert_mem_equal_goto(torture, data, + buf, ARRAY_SIZE(buf), + ret, done, + "Invalid content after padding smb2cli_read"); + + status = smb2_util_close(tree, h); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* + basic testing of SMB2 read +*/ +struct torture_suite *torture_smb2_read_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "read"); + + torture_suite_add_1smb2_test(suite, "eof", test_read_eof); + torture_suite_add_1smb2_test(suite, "position", test_read_position); + torture_suite_add_1smb2_test(suite, "dir", test_read_dir); + torture_suite_add_1smb2_test(suite, "access", test_read_access); + torture_suite_add_1smb2_test(suite, "bug14607", + test_read_bug14607); + + suite->description = talloc_strdup(suite, "SMB2-READ tests"); + + return suite; +} + +static bool test_aio_cancel(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle h; + uint8_t buf[64 * 1024]; + struct smb2_read r; + struct smb2_request *req = NULL; + int rc; + NTSTATUS status; + bool ret = true; + + ZERO_STRUCT(buf); + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + torture_assert_ntstatus_ok_goto( + tctx, + status, + ret, + done, + "torture_smb2_testfile failed\n"); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok_goto( + tctx, + status, + ret, + done, + "smb2_util_write failed\n"); + + status = smb2_util_close(tree, h); + torture_assert_ntstatus_ok_goto( + tctx, + status, + ret, + done, + "smb2_util_close failed\n"); + + status = torture_smb2_testfile_access( + tree, FNAME, &h, SEC_RIGHTS_FILE_ALL); + torture_assert_ntstatus_ok_goto( + tctx, + status, + ret, + done, + "torture_smb2_testfile_access failed\n"); + + r = (struct smb2_read) { + .in.file.handle = h, + .in.length = 1, + .in.offset = 0, + .in.min_count = 1, + }; + + req = smb2_read_send(tree, &r); + torture_assert_goto( + tctx, + req != NULL, + ret, + done, + "smb2_read_send failed\n"); + + while (!req->cancel.can_cancel) { + rc = tevent_loop_once(tctx->ev); + torture_assert_goto( + tctx, + rc == 0, + ret, + done, + "tevent_loop_once failed\n"); + } + + status = smb2_cancel(req); + torture_assert_ntstatus_ok_goto( + tctx, + status, + ret, + done, + "smb2_cancel failed\n"); + + status = smb2_read_recv(req, tree, &r); + torture_assert_ntstatus_ok_goto( + tctx, + status, + ret, + done, + "smb2_read_recv failed\n"); + + status = smb2_util_close(tree, h); + torture_assert_ntstatus_ok_goto( + tctx, + status, + ret, + done, + "smb2_util_close failed\n"); + +done: + smb2_util_unlink(tree, FNAME); + return ret; +} + +/* + * aio testing against share with VFS module "delay_inject" + */ +struct torture_suite *torture_smb2_aio_delay_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "aio_delay"); + + torture_suite_add_1smb2_test(suite, "aio_cancel", test_aio_cancel); + + suite->description = talloc_strdup(suite, "SMB2 delayed aio tests"); + + return suite; +} diff --git a/source4/torture/smb2/read_write.c b/source4/torture/smb2/read_write.c new file mode 100644 index 0000000..707a49b --- /dev/null +++ b/source4/torture/smb2/read_write.c @@ -0,0 +1,361 @@ +/* + Unix SMB/CIFS implementation. + SMB read/write torture tester + Copyright (C) Andrew Tridgell 1997-2003 + Copyright (C) Jelmer Vernooij 2006 + Copyright (C) David Mulder 2019 + + 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/>. +*/ +#include "includes.h" +#include "torture/smbtorture.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" + +#define CHECK_STATUS(_status, _expected) \ + torture_assert_ntstatus_equal_goto(torture, _status, _expected, \ + ret, done, "Incorrect status") + +#define CHECK_VALUE(v, correct) \ + torture_assert_int_equal_goto(torture, v, correct, \ + ret, done, "Incorrect value") + +#define FNAME "smb2_writetest.dat" + +static bool run_smb2_readwritetest(struct torture_context *tctx, + struct smb2_tree *t1, struct smb2_tree *t2) +{ + const char *lockfname = "torture2.lck"; + struct smb2_create f1 = {0}; + struct smb2_create f2 = {0}; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + int i; + uint8_t buf[131072]; + bool correct = true; + NTSTATUS status; + int ret = 0; + + ret = smb2_deltree(t1, lockfname); + torture_assert(tctx, ret != -1, "unlink failed"); + + f1.in.desired_access = SEC_FILE_ALL; + f1.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + f1.in.create_disposition = FILE_CREATE; + f1.in.fname = lockfname; + + status = smb2_create(t1, tctx, &f1); + torture_assert_ntstatus_ok_goto(tctx, status, correct, done, + talloc_asprintf(tctx, "first open read/write of %s failed (%s)", + lockfname, nt_errstr(status))); + h1 = f1.out.file.handle; + + f2.in.desired_access = SEC_FILE_READ_DATA; + f2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + f2.in.create_disposition = FILE_OPEN; + f2.in.fname = lockfname; + + status = smb2_create(t2, tctx, &f2); + torture_assert_ntstatus_ok_goto(tctx, status, correct, done, + talloc_asprintf(tctx, "second open read-only of %s failed (%s)", + lockfname, nt_errstr(status))); + h2 = f2.out.file.handle; + + torture_comment(tctx, "Checking data integrity over %d ops\n", + torture_numops); + + for (i = 0; i < torture_numops; i++) { + struct smb2_write w = {0}; + struct smb2_read r = {0}; + size_t buf_size = ((unsigned int)random()%(sizeof(buf)-1))+ 1; + + if (i % 10 == 0) { + if (torture_setting_bool(tctx, "progress", true)) { + torture_comment(tctx, "%d\r", i); fflush(stdout); + } + } + + generate_random_buffer(buf, buf_size); + + w.in.file.handle = h1; + w.in.offset = 0; + w.in.data.data = buf; + w.in.data.length = buf_size; + + status = smb2_write(t1, &w); + if (!NT_STATUS_IS_OK(status) || w.out.nwritten != buf_size) { + torture_comment(tctx, "write failed (%s)\n", + nt_errstr(status)); + torture_result(tctx, TORTURE_FAIL, + "wrote %d, expected %d\n", + (int)w.out.nwritten, (int)buf_size); + correct = false; + goto done; + } + + r.in.file.handle = h2; + r.in.offset = 0; + r.in.length = buf_size; + status = smb2_read(t2, tctx, &r); + if (!NT_STATUS_IS_OK(status) || r.out.data.length != buf_size) { + torture_comment(tctx, "read failed (%s)\n", + nt_errstr(status)); + torture_result(tctx, TORTURE_FAIL, + "read %d, expected %d\n", + (int)r.out.data.length, (int)buf_size); + correct = false; + goto done; + } + + torture_assert_mem_equal_goto(tctx, r.out.data.data, buf, + buf_size, correct, done, "read/write compare failed\n"); + } + + status = smb2_util_close(t2, h2); + torture_assert_ntstatus_ok_goto(tctx, status, correct, done, + talloc_asprintf(tctx, "close failed (%s)", nt_errstr(status))); + ZERO_STRUCT(h2); + + status = smb2_util_close(t1, h1); + torture_assert_ntstatus_ok_goto(tctx, status, correct, done, + talloc_asprintf(tctx, "close failed (%s)", nt_errstr(status))); + ZERO_STRUCT(h1); + +done: + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(t2, h2); + } + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(t1, h1); + } + + status = smb2_util_unlink(t1, lockfname); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "unlink failed (%s)", nt_errstr(status)); + } + + return correct; +} + + +static bool run_smb2_wrap_readwritetest(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return run_smb2_readwritetest(tctx, tree1, tree1); +} + +static bool test_rw_invalid(struct torture_context *torture, struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[64*1024]; + struct smb2_read rd; + struct smb2_write w = {0}; + union smb_setfileinfo sfinfo; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + + ZERO_STRUCT(buf); + + smb2_util_unlink(tree, FNAME); + + status = torture_smb2_testfile(tree, FNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + /* set delete-on-close */ + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.disposition_info.in.delete_on_close = 1; + sfinfo.generic.in.file.handle = h; + status = smb2_setinfo_file(tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 10; + rd.in.offset = 0; + rd.in.min_count = 1; + + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, 10); + + rd.in.min_count = 0; + rd.in.length = 10; + rd.in.offset = sizeof(buf); + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = sizeof(buf); + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, 0); + + rd.in.min_count = 0; + rd.in.length = 1; + rd.in.offset = INT64_MAX - 1; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = INT64_MAX; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(rd.out.data.length, 0); + + rd.in.min_count = 0; + rd.in.length = 1; + rd.in.offset = INT64_MAX; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = (uint64_t)INT64_MAX + 1; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = (uint64_t)INT64_MIN; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = (uint64_t)(int64_t)-1; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = (uint64_t)(int64_t)-2; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + rd.in.min_count = 0; + rd.in.length = 0; + rd.in.offset = (uint64_t)(int64_t)-3; + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = (int64_t)-1; + w.in.data.data = buf; + w.in.data.length = ARRAY_SIZE(buf); + + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = (int64_t)-2; + w.in.data.data = buf; + w.in.data.length = ARRAY_SIZE(buf); + + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = INT64_MIN; + w.in.data.data = buf; + w.in.data.length = 1; + + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = INT64_MIN; + w.in.data.data = buf; + w.in.data.length = 0; + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = INT64_MAX; + w.in.data.data = buf; + w.in.data.length = 0; + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(w.out.nwritten, 0); + + w.in.file.handle = h; + w.in.offset = INT64_MAX; + w.in.data.data = buf; + w.in.data.length = 1; + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = (uint64_t)INT64_MAX + 1; + w.in.data.data = buf; + w.in.data.length = 0; + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = 0xfffffff0000; /* MAXFILESIZE */ + w.in.data.data = buf; + w.in.data.length = 1; + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + w.in.file.handle = h; + w.in.offset = 0xfffffff0000 - 1; /* MAXFILESIZE - 1 */ + w.in.data.data = buf; + w.in.data.length = 1; + status = smb2_write(tree, &w); + if (TARGET_IS_SAMBA3(torture) || TARGET_IS_SAMBA4(torture)) { + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(w.out.nwritten, 1); + } else { + CHECK_STATUS(status, NT_STATUS_DISK_FULL); + } + + w.in.file.handle = h; + w.in.offset = 0xfffffff0000; /* MAXFILESIZE */ + w.in.data.data = buf; + w.in.data.length = 0; + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VALUE(w.out.nwritten, 0); + +done: + talloc_free(tmp_ctx); + return ret; +} + +struct torture_suite *torture_smb2_readwrite_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "rw"); + + torture_suite_add_2smb2_test(suite, "rw1", run_smb2_readwritetest); + torture_suite_add_2smb2_test(suite, "rw2", run_smb2_wrap_readwritetest); + torture_suite_add_1smb2_test(suite, "invalid", test_rw_invalid); + + suite->description = talloc_strdup(suite, "SMB2 Samba4 Read/Write"); + + return suite; +} diff --git a/source4/torture/smb2/rename.c b/source4/torture/smb2/rename.c new file mode 100644 index 0000000..1b3dca8 --- /dev/null +++ b/source4/torture/smb2/rename.c @@ -0,0 +1,1614 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 rename test suite + + Copyright (C) Christian Ambach 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +#include "librpc/gen_ndr/security.h" + +#define CHECK_VAL(v, correct) \ + do { \ + if ((v) != (correct)) { \ + torture_result(torture, \ + TORTURE_FAIL, \ + "(%s): wrong value for %s got " \ + "0x%llx - should be 0x%llx\n", \ + __location__, #v, \ + (unsigned long long)v, \ + (unsigned long long)correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_CREATED(__io, __created, __attribute) \ + do { \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ + CHECK_VAL((__io)->out.size, 0); \ + CHECK_VAL((__io)->out.file_attr, (__attribute)); \ + CHECK_VAL((__io)->out.reserved2, 0); \ + } while(0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(torture, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define BASEDIR "test_rename" + +/* + * basic testing of rename: open file with DELETE access + * this should pass + */ + +static bool torture_smb2_rename_simple(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + union smb_fileinfo fi; + struct smb2_handle h1; + + ZERO_STRUCT(h1); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL|SEC_STD_DELETE; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = io.smb2.out.file.handle; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Checking for new filename\n"); + + ZERO_STRUCT(fi); + fi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + fi.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree1, torture, &fi); + CHECK_STATUS(status, NT_STATUS_OK); + + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(h1); + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (h1.data[0] || h1.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + } + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* + * basic testing of rename, this time do not request DELETE access + * for the file, this should fail + */ + +static bool torture_smb2_rename_simple2(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + struct smb2_handle h1; + + ZERO_STRUCT(h1); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_ALL; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = io.smb2.out.file.handle; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(h1); + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (h1.data[0] || h1.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + } + smb2_deltree(tree1, BASEDIR); + return ret; +} + + +/* + * testing of rename with no sharing allowed on file + * this should work + */ + +static bool torture_smb2_rename_no_sharemode(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + union smb_fileinfo fi; + struct smb2_handle h1; + + ZERO_STRUCT(h1); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = 0x0017019f; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = io.smb2.out.file.handle; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Checking for new filename\n"); + + ZERO_STRUCT(fi); + fi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + fi.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree1, torture, &fi); + CHECK_STATUS(status, NT_STATUS_OK); + + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(h1); + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (h1.data[0] || h1.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + } + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* + * testing of rename when opening parent dir with delete access and delete + * sharing allowed + * should result in sharing violation + */ + +static bool torture_smb2_rename_with_delete_access(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + struct smb2_handle fh, dh; + + ZERO_STRUCT(fh); + ZERO_STRUCT(dh); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + torture_comment(torture, "Opening parent directory\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_STD_DELETE | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_EXECUTE | SEC_FILE_WRITE_EA | + SEC_FILE_READ_EA | SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + dh = io.smb2.out.file.handle; + + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_STD_DELETE | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_WRITE_EA | SEC_FILE_READ_EA | + SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + fh = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = fh; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(fh); + + torture_comment(torture, "Closing directory\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(dh); + + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (fh.data[0] || fh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + } + if (dh.data[0] || dh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + } + + smb2_deltree(tree1, BASEDIR); + return ret; +} + + +/* + * testing of rename with delete access on parent dir + * this is a variation of the test above: parent dir is opened + * without share_delete, so rename must fail + */ + +static bool torture_smb2_rename_with_delete_access2(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + struct smb2_handle fh, dh; + + ZERO_STRUCT(fh); + ZERO_STRUCT(dh); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + torture_comment(torture, "Opening parent directory\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_STD_DELETE | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_EXECUTE | SEC_FILE_WRITE_EA | + SEC_FILE_READ_EA | SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + dh = io.smb2.out.file.handle; + + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_STD_DELETE | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_WRITE_EA | SEC_FILE_READ_EA | + SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + fh = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = fh; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(fh); + + torture_comment(torture, "Closing directory\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(dh); + + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (fh.data[0] || fh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + } + if (dh.data[0] || dh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + } + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* + * testing of rename when opening parent dir with no delete access and delete + * sharing allowed + * this should pass + */ + +static bool torture_smb2_rename_no_delete_access(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + union smb_fileinfo fi; + struct smb2_handle fh, dh; + + ZERO_STRUCT(fh); + ZERO_STRUCT(dh); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + torture_comment(torture, "Opening parent directory\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_EXECUTE | SEC_FILE_WRITE_EA | + SEC_FILE_READ_EA | SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + dh = io.smb2.out.file.handle; + + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_STD_DELETE | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_WRITE_EA | SEC_FILE_READ_EA | + SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + fh = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = fh; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Checking for new filename\n"); + + ZERO_STRUCT(fi); + fi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + fi.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree1, torture, &fi); + CHECK_STATUS(status, NT_STATUS_OK); + + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(fh); + + torture_comment(torture, "Closing directory\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(dh); + + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (fh.data[0] || fh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + } + if (dh.data[0] || dh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + } + + smb2_deltree(tree1, BASEDIR); + return ret; +} + + +/* + * testing of rename with no delete access on parent dir + * this is the negative case of the test above: parent dir is opened + * without share_delete, so rename must fail + */ + +static bool torture_smb2_rename_no_delete_access2(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + struct smb2_handle fh, dh; + + ZERO_STRUCT(fh); + ZERO_STRUCT(dh); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + torture_comment(torture, "Opening parent directory\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_EXECUTE | SEC_FILE_WRITE_EA | + SEC_FILE_READ_EA | SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + dh = io.smb2.out.file.handle; + + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_STD_WRITE_DAC | + SEC_STD_READ_CONTROL | SEC_STD_DELETE | SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_ATTRIBUTE | SEC_FILE_WRITE_EA | SEC_FILE_READ_EA | + SEC_FILE_APPEND_DATA | SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + fh = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = fh; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(fh); + + torture_comment(torture, "Closing directory\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(dh); + + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (fh.data[0] || fh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + } + if (dh.data[0] || dh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + } + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +/* + * this is a replay of how Word 2010 saves a file + * this should pass + */ + +static bool torture_smb2_rename_msword(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + union smb_fileinfo fi; + struct smb2_handle fh, dh; + + ZERO_STRUCT(fh); + ZERO_STRUCT(dh); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + smb2_util_mkdir(tree1, BASEDIR); + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = 0x0017019f; + io.smb2.in.create_options = 0x60; + io.smb2.in.file_attributes = 0; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + fh = io.smb2.out.file.handle; + + torture_comment(torture, "Opening parent directory\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = 0x00100080; + io.smb2.in.create_options = 0x00800021; + io.smb2.in.file_attributes = 0; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + dh = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming test file\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = fh; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "\\newname.txt"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Checking for new filename\n"); + + ZERO_STRUCT(fi); + fi.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + fi.generic.in.file.handle = fh; + status = smb2_getinfo_file(tree1, torture, &fi); + CHECK_STATUS(status, NT_STATUS_OK); + + + torture_comment(torture, "Closing test file\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(fh); + + torture_comment(torture, "Closing directory\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(dh); + + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (fh.data[0] || fh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = fh; + status = smb2_close(tree1, &(cl.smb2)); + } + if (dh.data[0] || dh.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = dh; + status = smb2_close(tree1, &(cl.smb2)); + } + + smb2_deltree(tree1, BASEDIR); + return ret; +} + +static bool torture_smb2_rename_dir_openfile(struct torture_context *torture, + struct smb2_tree *tree1) +{ + bool ret = true; + NTSTATUS status; + union smb_open io; + union smb_close cl; + union smb_setfileinfo sinfo; + struct smb2_handle d1, h1; + + ZERO_STRUCT(d1); + ZERO_STRUCT(h1); + + smb2_deltree(tree1, BASEDIR); + smb2_util_rmdir(tree1, BASEDIR); + + torture_comment(torture, "Creating base directory\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = 0x0017019f; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + d1 = io.smb2.out.file.handle; + + torture_comment(torture, "Creating test file\n"); + + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = 0x0017019f; + io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = BASEDIR "\\file.txt"; + + status = smb2_create(tree1, torture, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + torture_comment(torture, "Renaming directory\n"); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = d1; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = + BASEDIR "-new"; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + + torture_comment(torture, "Closing directory\n"); + + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = d1; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(d1); + + torture_comment(torture, "Closing test file\n"); + + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(h1); + +done: + + torture_comment(torture, "Cleaning up\n"); + + if (h1.data[0] || h1.data[1]) { + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + status = smb2_close(tree1, &(cl.smb2)); + } + smb2_deltree(tree1, BASEDIR); + return ret; +} + +struct rename_one_dir_cycle_state { + struct tevent_context *ev; + struct smb2_tree *tree; + struct smb2_handle file; + const char *base_name; + char *new_name; + unsigned *rename_counter; + + unsigned current; + unsigned max; + union smb_setfileinfo sinfo; +}; + +static void rename_one_dir_cycle_done(struct smb2_request *subreq); + +static struct tevent_req *rename_one_dir_cycle_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smb2_tree *tree, + struct smb2_handle file, + unsigned max_renames, + const char *base_name, + unsigned *rename_counter) +{ + struct tevent_req *req; + struct rename_one_dir_cycle_state *state; + struct smb2_request *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct rename_one_dir_cycle_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->tree = tree; + state->file = file; + state->base_name = base_name; + state->rename_counter = rename_counter; + state->current = 0; + state->max = max_renames; + + ZERO_STRUCT(state->sinfo); + state->sinfo.rename_information.level = + RAW_SFILEINFO_RENAME_INFORMATION; + state->sinfo.rename_information.in.file.handle = state->file; + state->sinfo.rename_information.in.overwrite = 0; + state->sinfo.rename_information.in.root_fid = 0; + + state->new_name = talloc_asprintf( + state, "%s-%u", state->base_name, state->current); + if (tevent_req_nomem(state->new_name, req)) { + return tevent_req_post(req, ev); + } + state->sinfo.rename_information.in.new_name = state->new_name; + + subreq = smb2_setinfo_file_send(state->tree, &state->sinfo); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + subreq->async.fn = rename_one_dir_cycle_done; + subreq->async.private_data = req; + return req; +} + +static void rename_one_dir_cycle_done(struct smb2_request *subreq) +{ + struct tevent_req *req = talloc_get_type_abort( + subreq->async.private_data, struct tevent_req); + struct rename_one_dir_cycle_state *state = tevent_req_data( + req, struct rename_one_dir_cycle_state); + NTSTATUS status; + + status = smb2_setinfo_recv(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + TALLOC_FREE(state->new_name); + + *state->rename_counter += 1; + + state->current += 1; + if (state->current >= state->max) { + tevent_req_done(req); + return; + } + + ZERO_STRUCT(state->sinfo); + state->sinfo.rename_information.level = + RAW_SFILEINFO_RENAME_INFORMATION; + state->sinfo.rename_information.in.file.handle = state->file; + state->sinfo.rename_information.in.overwrite = 0; + state->sinfo.rename_information.in.root_fid = 0; + + state->new_name = talloc_asprintf( + state, "%s-%u", state->base_name, state->current); + if (tevent_req_nomem(state->new_name, req)) { + return; + } + state->sinfo.rename_information.in.new_name = state->new_name; + + subreq = smb2_setinfo_file_send(state->tree, &state->sinfo); + if (tevent_req_nomem(subreq, req)) { + return; + } + subreq->async.fn = rename_one_dir_cycle_done; + subreq->async.private_data = req; +} + +static NTSTATUS rename_one_dir_cycle_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +struct rename_dir_bench_state { + struct tevent_context *ev; + struct smb2_tree *tree; + const char *base_name; + unsigned max_renames; + unsigned *rename_counter; + + struct smb2_create io; + union smb_setfileinfo sinfo; + struct smb2_close cl; + + struct smb2_handle file; +}; + +static void rename_dir_bench_opened(struct smb2_request *subreq); +static void rename_dir_bench_renamed(struct tevent_req *subreq); +static void rename_dir_bench_set_doc(struct smb2_request *subreq); +static void rename_dir_bench_closed(struct smb2_request *subreq); + +static struct tevent_req *rename_dir_bench_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smb2_tree *tree, + const char *base_name, + unsigned max_renames, + unsigned *rename_counter) +{ + struct tevent_req *req; + struct rename_dir_bench_state *state; + struct smb2_request *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct rename_dir_bench_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->tree = tree; + state->base_name = base_name; + state->max_renames = max_renames; + state->rename_counter = rename_counter; + + ZERO_STRUCT(state->io); + state->io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + state->io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + state->io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + state->io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + state->io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + state->io.in.fname = state->base_name; + + subreq = smb2_create_send(state->tree, &state->io); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + subreq->async.fn = rename_dir_bench_opened; + subreq->async.private_data = req; + return req; +} + +static void rename_dir_bench_opened(struct smb2_request *subreq) +{ + struct tevent_req *req = talloc_get_type_abort( + subreq->async.private_data, struct tevent_req); + struct rename_dir_bench_state *state = tevent_req_data( + req, struct rename_dir_bench_state); + struct smb2_create *io; + struct tevent_req *subreq2; + NTSTATUS status; + + io = talloc(state, struct smb2_create); + if (tevent_req_nomem(io, req)) { + return; + } + + status = smb2_create_recv(subreq, io, io); + if (tevent_req_nterror(req, status)) { + return; + } + state->file = io->out.file.handle; + TALLOC_FREE(io); + + subreq2 = rename_one_dir_cycle_send( + state, state->ev, state->tree, state->file, + state->max_renames, state->base_name, + state->rename_counter); + if (tevent_req_nomem(subreq2, req)) { + return; + } + tevent_req_set_callback(subreq2, rename_dir_bench_renamed, req); +} + +static void rename_dir_bench_renamed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rename_dir_bench_state *state = tevent_req_data( + req, struct rename_dir_bench_state); + struct smb2_request *subreq2; + NTSTATUS status; + + status = rename_one_dir_cycle_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + ZERO_STRUCT(state->sinfo); + state->sinfo.disposition_info.level = + RAW_SFILEINFO_DISPOSITION_INFORMATION; + state->sinfo.disposition_info.in.file.handle = state->file; + state->sinfo.disposition_info.in.delete_on_close = true; + + subreq2 = smb2_setinfo_file_send(state->tree, &state->sinfo); + if (tevent_req_nomem(subreq2, req)) { + return; + } + subreq2->async.fn = rename_dir_bench_set_doc; + subreq2->async.private_data = req; +} + +static void rename_dir_bench_set_doc(struct smb2_request *subreq) +{ + struct tevent_req *req = talloc_get_type_abort( + subreq->async.private_data, struct tevent_req); + struct rename_dir_bench_state *state = tevent_req_data( + req, struct rename_dir_bench_state); + NTSTATUS status; + + status = smb2_setinfo_recv(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + ZERO_STRUCT(state->cl); + state->cl.in.file.handle = state->file; + + subreq = smb2_close_send(state->tree, &state->cl); + if (tevent_req_nomem(subreq, req)) { + return; + } + subreq->async.fn = rename_dir_bench_closed; + subreq->async.private_data = req; +} + +static void rename_dir_bench_closed(struct smb2_request *subreq) +{ + struct tevent_req *req = talloc_get_type_abort( + subreq->async.private_data, struct tevent_req); + struct smb2_close cl; + NTSTATUS status; + + status = smb2_close_recv(subreq, &cl); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +static NTSTATUS rename_dir_bench_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +struct rename_dirs_bench_state { + unsigned num_reqs; + unsigned num_done; +}; + +static void rename_dirs_bench_done(struct tevent_req *subreq); + +static struct tevent_req *rename_dirs_bench_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smb2_tree *tree, + const char *base_name, + unsigned num_parallel, + unsigned max_renames, + unsigned *rename_counter) +{ + struct tevent_req *req; + struct rename_dirs_bench_state *state; + unsigned i; + + req = tevent_req_create(mem_ctx, &state, + struct rename_dirs_bench_state); + if (req == NULL) { + return NULL; + } + state->num_reqs = num_parallel; + state->num_done = 0; + + for (i=0; i<num_parallel; i++) { + struct tevent_req *subreq; + char *sub_base; + + sub_base = talloc_asprintf(state, "%s-%u", base_name, i); + if (tevent_req_nomem(sub_base, req)) { + return tevent_req_post(req, ev); + } + + subreq = rename_dir_bench_send(state, ev, tree, sub_base, + max_renames, rename_counter); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, rename_dirs_bench_done, req); + } + return req; +} + +static void rename_dirs_bench_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rename_dirs_bench_state *state = tevent_req_data( + req, struct rename_dirs_bench_state); + NTSTATUS status; + + status = rename_dir_bench_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->num_done += 1; + if (state->num_done >= state->num_reqs) { + tevent_req_done(req); + } +} + +static NTSTATUS rename_dirs_bench_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static bool torture_smb2_rename_dir_bench(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct tevent_req *req; + NTSTATUS status; + unsigned counter = 0; + bool ret; + + req = rename_dirs_bench_send(tctx, tctx->ev, tree, "dir", 3, 10, + &counter); + torture_assert(tctx, req != NULL, "rename_dirs_bench_send failed"); + + ret = tevent_req_poll(req, tctx->ev); + torture_assert(tctx, ret, "tevent_req_poll failed"); + + status = rename_dirs_bench_recv(req); + torture_comment(tctx, "rename_dirs_bench returned %s\n", + nt_errstr(status)); + TALLOC_FREE(req); + torture_assert_ntstatus_ok(tctx, status, "bench failed"); + return true; +} + +static bool test_smb2_close_full_information(struct torture_context *torture, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + union smb_close cl; + struct smb2_create io = {0}; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + union smb_setfileinfo sinfo; + NTSTATUS status; + const char *fname_src = "request.dat"; + const char *fname_dst = "renamed.dat"; + bool ret = true; + + /* Start with a tidy share. */ + smb2_util_unlink(tree1, fname_src); + smb2_util_unlink(tree1, fname_dst); + + /* Create the test file, and leave it open. */ + io.in.fname = fname_src; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tree1, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + /* Open the test file on the second connection. */ + ZERO_STRUCT(io); + io.in.fname = fname_src; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree2, tree2, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + + /* Now open for rename on the first connection. */ + ZERO_STRUCT(io); + io.in.fname = fname_src; + io.in.desired_access = SEC_STD_DELETE | SEC_FILE_READ_ATTRIBUTE; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree1, tree1, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + + /* Do the rename. */ + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h3; + sinfo.rename_information.in.new_name = fname_dst; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* And close h3. */ + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h3; + status = smb2_close(tree1, &cl.smb2); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(h3); + + /* + * Close h1 with SMB2_CLOSE_FLAGS_FULL_INFORMATION. + * Ensure we get data. + */ + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h1; + cl.smb2.in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION; + status = smb2_close(tree1, &cl.smb2); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(h1); + CHECK_VAL(cl.smb2.out.file_attr, 0x20); + + /* + * Wait 3 seconds for name change to propagate + * to the other connection. + */ + sleep(3); + + /* + * Close h2 with SMB2_CLOSE_FLAGS_FULL_INFORMATION. + * This is on connection2. + * Ensure we get data. + */ + ZERO_STRUCT(cl.smb2); + cl.smb2.level = RAW_CLOSE_SMB2; + cl.smb2.in.file.handle = h2; + cl.smb2.in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION; + status = smb2_close(tree2, &cl.smb2); + CHECK_STATUS(status, NT_STATUS_OK); + ZERO_STRUCT(h2); + CHECK_VAL(cl.smb2.out.file_attr, 0x20); + + done: + + if (h1.data[0] != 0 || h1.data[1] != 0) { + smb2_util_close(tree1, h1); + } + if (h2.data[0] != 0 || h2.data[1] != 0) { + smb2_util_close(tree2, h2); + } + if (h3.data[0] != 0 || h3.data[1] != 0) { + smb2_util_close(tree1, h3); + } + + smb2_util_unlink(tree1, fname_src); + smb2_util_unlink(tree1, fname_dst); + + return ret; +} + +/* + basic testing of SMB2 rename + */ +struct torture_suite *torture_smb2_rename_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "rename"); + + torture_suite_add_1smb2_test(suite, "simple", + torture_smb2_rename_simple); + + torture_suite_add_1smb2_test(suite, "simple_nodelete", + torture_smb2_rename_simple2); + + torture_suite_add_1smb2_test(suite, "no_sharing", + torture_smb2_rename_no_sharemode); + + torture_suite_add_1smb2_test(suite, + "share_delete_and_delete_access", + torture_smb2_rename_with_delete_access); + + torture_suite_add_1smb2_test(suite, + "no_share_delete_but_delete_access", + torture_smb2_rename_with_delete_access2); + + torture_suite_add_1smb2_test(suite, + "share_delete_no_delete_access", + torture_smb2_rename_no_delete_access); + + torture_suite_add_1smb2_test(suite, + "no_share_delete_no_delete_access", + torture_smb2_rename_no_delete_access2); + + torture_suite_add_1smb2_test(suite, + "msword", + torture_smb2_rename_msword); + + torture_suite_add_1smb2_test( + suite, "rename_dir_openfile", + torture_smb2_rename_dir_openfile); + + torture_suite_add_1smb2_test(suite, + "rename_dir_bench", + torture_smb2_rename_dir_bench); + + torture_suite_add_2smb2_test(suite, + "close-full-information", + test_smb2_close_full_information); + + suite->description = talloc_strdup(suite, "smb2.rename tests"); + + return suite; +} diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c new file mode 100644 index 0000000..b70db37 --- /dev/null +++ b/source4/torture/smb2/replay.c @@ -0,0 +1,5515 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 replay + + Copyright (C) Anubhav Rakshit 2014 + Copyright (C) Stefan Metzmacher 2014 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" +#include "lib/cmdline/cmdline.h" +#include "auth/credentials/credentials.h" +#include "libcli/security/security.h" +#include "libcli/resolve/resolve.h" +#include "lib/param/param.h" +#include "lib/events/events.h" +#include "oplock_break_handler.h" +#include "lease_break_handler.h" + +#define CHECK_VAL(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ + nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_CREATED(__io, __created, __attribute) \ + do { \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ + CHECK_VAL((__io)->out.size, 0); \ + CHECK_VAL((__io)->out.file_attr, (__attribute)); \ + CHECK_VAL((__io)->out.reserved2, 0); \ + } while(0) + +#define CHECK_HANDLE(__h1, __h2) \ + do { \ + CHECK_VAL((__h1)->data[0], (__h2)->data[0]); \ + CHECK_VAL((__h1)->data[1], (__h2)->data[1]); \ + } while(0) + +#define __IO_OUT_VAL(__io1, __io2, __m) \ + CHECK_VAL((__io1)->out.__m, (__io2)->out.__m) + +#define CHECK_CREATE_OUT(__io1, __io2) \ + do { \ + CHECK_HANDLE(&(__io1)->out.file.handle, \ + &(__io2)->out.file.handle); \ + __IO_OUT_VAL(__io1, __io2, oplock_level); \ + __IO_OUT_VAL(__io1, __io2, create_action); \ + __IO_OUT_VAL(__io1, __io2, create_time); \ + __IO_OUT_VAL(__io1, __io2, access_time); \ + __IO_OUT_VAL(__io1, __io2, write_time); \ + __IO_OUT_VAL(__io1, __io2, change_time); \ + __IO_OUT_VAL(__io1, __io2, alloc_size); \ + __IO_OUT_VAL(__io1, __io2, size); \ + __IO_OUT_VAL(__io1, __io2, file_attr); \ + __IO_OUT_VAL(__io1, __io2, durable_open); \ + __IO_OUT_VAL(__io1, __io2, durable_open_v2); \ + __IO_OUT_VAL(__io1, __io2, persistent_open); \ + __IO_OUT_VAL(__io1, __io2, timeout); \ + __IO_OUT_VAL(__io1, __io2, blobs.num_blobs); \ + if ((__io1)->out.oplock_level == SMB2_OPLOCK_LEVEL_LEASE) { \ + __IO_OUT_VAL(__io1, __io2, lease_response.lease_state);\ + __IO_OUT_VAL(__io1, __io2, lease_response.lease_key.data[0]);\ + __IO_OUT_VAL(__io1, __io2, lease_response.lease_key.data[1]);\ + } \ + } while(0) + +#define WAIT_FOR_ASYNC_RESPONSE(__tctx, __req) do { \ + torture_comment((__tctx), "Waiting for async response: %s\n", #__req); \ + while (!(__req)->cancel.can_cancel && (__req)->state <= SMB2_REQUEST_RECV) { \ + if (tevent_loop_once((__tctx)->ev) != 0) { \ + break; \ + } \ + } \ +} while(0) + +#define BASEDIR "replaytestdir" + +/** + * Test what happens when SMB2_FLAGS_REPLAY_OPERATION is enabled for various + * commands. We want to verify if the server returns an error code or not. + */ +static bool test_replay_commands(struct torture_context *tctx, struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_handle h; + uint8_t buf[200]; + struct smb2_read rd; + union smb_setfileinfo sfinfo; + union smb_fileinfo qfinfo; + union smb_ioctl ioctl; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + struct smb2_flush f; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + const char *fname = BASEDIR "\\replay_commands.dat"; + struct smb2_transport *transport = tree->session->transport; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "Replay tests\n"); + } + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + smb2cli_session_start_replay(tree->session->smbXcli); + + torture_comment(tctx, "Try Commands with Replay Flags Enabled\n"); + + torture_comment(tctx, "Trying create\n"); + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + /* + * Wireshark shows that the response has SMB2_FLAGS_REPLAY_OPERATION + * flags set. The server should ignore this flag. + */ + + torture_comment(tctx, "Trying write\n"); + status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + f = (struct smb2_flush) { + .in.file.handle = h + }; + torture_comment(tctx, "Trying flush\n"); + status = smb2_flush(tree, &f); + CHECK_STATUS(status, NT_STATUS_OK); + + rd = (struct smb2_read) { + .in.file.handle = h, + .in.length = 10, + .in.offset = 0, + .in.min_count = 1 + }; + torture_comment(tctx, "Trying read\n"); + status = smb2_read(tree, tmp_ctx, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(rd.out.data.length, 10); + + sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION; + sfinfo.position_information.in.file.handle = h; + sfinfo.position_information.in.position = 0x1000; + torture_comment(tctx, "Trying setinfo\n"); + status = smb2_setinfo_file(tree, &sfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + qfinfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_POSITION_INFORMATION, + .generic.in.file.handle = h + }; + torture_comment(tctx, "Trying getinfo\n"); + status = smb2_getinfo_file(tree, tmp_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(qfinfo.position_information.out.position, 0x1000); + + ioctl = (union smb_ioctl) { + .smb2.level = RAW_IOCTL_SMB2, + .smb2.in.file.handle = h, + .smb2.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID, + .smb2.in.max_output_response = 64, + .smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL + }; + torture_comment(tctx, "Trying ioctl\n"); + status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2); + CHECK_STATUS(status, NT_STATUS_OK); + + lck = (struct smb2_lock) { + .in.locks = el, + .in.lock_count = 0x0001, + .in.lock_sequence = 0x00000000, + .in.file.handle = h + }; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + torture_comment(tctx, "Trying lock\n"); + el[0].offset = 0x0000000000000000; + el[0].length = 0x0000000000000100; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + CHECK_VAL(break_info.count, 0); +done: + smb2cli_session_stop_replay(tree->session->smbXcli); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + + talloc_free(tmp_ctx); + + return ret; +} + +/** + * Test replay detection without create GUID on single channel. + * Regular creates can not be replayed. + * The return code is unaffected of the REPLAY_OPERATION flag. + */ +static bool test_replay_regular(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + uint32_t perms = 0; + bool ret = true; + const char *fname = BASEDIR "\\replay_regular.dat"; + struct smb2_transport *transport = tree->session->transport; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h); + CHECK_VAL(break_info.count, 0); + + torture_comment(tctx, "No replay detection for regular create\n"); + + perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE | + SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE | + SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_DATA; + + io = (struct smb2_create) { + .in.desired_access = perms, + .in.file_attributes = 0, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.share_access = NTCREATEX_SHARE_ACCESS_DELETE, + .in.create_options = 0x0, + .in.fname = fname + }; + + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, tctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + CHECK_VAL(break_info.count, 0); + + smb2_util_close(tree, *h); + h = NULL; + smb2_util_unlink(tree, fname); + + /* + * Same experiment with different create disposition. + */ + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, tctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + CHECK_VAL(break_info.count, 0); + + smb2_util_close(tree, *h); + h = NULL; + smb2_util_unlink(tree, fname); + + /* + * Now with more generous share mode. + */ + io.in.share_access = smb2_util_share_access("RWD"); + status = smb2_create(tree, tctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, tctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(break_info.count, 0); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test Durability V2 Create Replay Detection on Single Channel. + */ +static bool test_replay_dhv2_oplock1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io, ref1; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay_dhv2_oplock1.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 on Single " + "Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + ref1 = io; + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + if (share_is_so) { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.timeout, 0); + } else { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.timeout, 300*1000); + } + + /* + * Replay Durable V2 Create on single channel + */ + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATE_OUT(&io, &ref1); + CHECK_VAL(break_info.count, 0); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test Durability V2 Create Replay Detection on Single Channel. + * Hand in a different oplock level in the replay. + * Server responds with the handed in oplock level and + * corresponding durable status, but does not change the + * oplock level or durable status of the opened file. + */ +static bool test_replay_dhv2_oplock2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io, ref1, ref2; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay_dhv2_oplock2.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 on Single " + "Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + ref1 = io; + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + if (share_is_so) { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.timeout, 0); + } else { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.timeout, 300*1000); + } + + /* + * Replay durable v2 create on single channel: + * + * Replay the create with a different oplock (none). + * The server replies with the requested oplock level + * and also only replies with durable handle based + * on whether it could have been granted based on + * the requested oplock type. + */ + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + /* + * Adapt the response to the exepected values + */ + ref2 = ref1; + ref2.out.oplock_level = smb2_util_oplock_level(""); + ref2.out.durable_open_v2 = false; + ref2.out.timeout = 0; + ref2.out.blobs.num_blobs = 0; + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATE_OUT(&io, &ref2); + CHECK_VAL(break_info.count, 0); + + /* + * Prove that the open file still has a batch oplock + * by breaking it with another open. + */ + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = GUID_random(); + io.in.timeout = UINT32_MAX; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + if (!share_is_so) { + CHECK_VAL(break_info.count, 1); + CHECK_HANDLE(&break_info.handle, &ref1.out.file.handle); + CHECK_VAL(break_info.level, smb2_util_oplock_level("s")); + torture_reset_break_info(tctx, &break_info); + } + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test Durability V2 Create Replay Detection on Single Channel. + * Replay with a different share mode. The share mode of + * the opened file is not changed by this. + */ +static bool test_replay_dhv2_oplock3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io, ref1; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay_dhv2_oplock3.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 on Single " + "Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + ref1 = io; + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + if (share_is_so) { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.timeout, 0); + } else { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.timeout, 300*1000); + } + + /* + * Replay durable v2 create on single channel: + * + * Replay the create with a different share mode. + * The server replies with the requested share + * mode instead of that which is associated to + * the handle. + */ + smb2_oplock_create_share(&io, fname, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATE_OUT(&io, &ref1); + CHECK_VAL(break_info.count, 0); + + /* + * In order to prove that the different share mode in the + * replayed create had no effect on the open file handle, + * show that a new create yields NT_STATUS_SHARING_VIOLATION. + */ + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = GUID_random(); + io.in.timeout = UINT32_MAX; + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + if (!share_is_so) { + CHECK_VAL(break_info.count, 1); + CHECK_HANDLE(&break_info.handle, &ref1.out.file.handle); + CHECK_VAL(break_info.level, smb2_util_oplock_level("s")); + torture_reset_break_info(tctx, &break_info); + } + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test Durability V2 Create Replay Detection on Single Channel. + * Create with an oplock, and replay with a lease. + */ +static bool test_replay_dhv2_oplock_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay_dhv2_oplock1.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + uint32_t server_capabilities; + struct smb2_lease ls; + uint64_t lease_key; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport->conn); + if (!(server_capabilities & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 on Single " + "Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + if (share_is_so) { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.timeout, 0); + } else { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.timeout, 300*1000); + } + + /* + * Replay Durable V2 Create on single channel + * but replay it with a lease instead of an oplock. + */ + lease_key = random(); + smb2_lease_create(&io, &ls, false /* dir */, fname, + lease_key, smb2_util_lease_state("RH")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + + +/** + * Test durability v2 create replay detection on single channel. + * Variant with leases instead of oplocks: + * - open a file with a rh lease + * - upgrade to a rwh lease with a second create + * - replay the first create. + * ==> it gets back the upgraded lease level + */ +static bool test_replay_dhv2_lease1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io1, io2, ref1; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay2_lease1.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + uint32_t server_capabilities; + struct smb2_lease ls1, ls2; + uint64_t lease_key; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport->conn); + if (!(server_capabilities & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease " + "on Single Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key = random(); + + smb2_lease_create(&io1, &ls1, false /* dir */, fname, + lease_key, smb2_util_lease_state("RH")); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + ref1 = io1; + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key); + if (share_is_so) { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("R")); + CHECK_VAL(io1.out.durable_open_v2, false); + CHECK_VAL(io1.out.timeout, 0); + } else { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("RH")); + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + } + + /* + * Upgrade the lease to RWH + */ + smb2_lease_create(&io2, &ls2, false /* dir */, fname, + lease_key, smb2_util_lease_state("RHW")); + io2.in.durable_open = false; + io2.in.durable_open_v2 = true; + io2.in.persistent_open = false; + io2.in.create_guid = GUID_random(); /* new guid... */ + io2.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io2.out.file.handle; + h2 = &_h2; + + /* + * Replay Durable V2 Create on single channel. + * We get the io from open #1 but with the + * upgraded lease. + */ + + /* adapt expected lease in response */ + if (!share_is_so) { + ref1.out.lease_response.lease_state = + smb2_util_lease_state("RHW"); + } + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io1); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATE_OUT(&io1, &ref1); + CHECK_VAL(break_info.count, 0); + +done: + smb2cli_session_stop_replay(tree->session->smbXcli); + + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + if (h2 != NULL) { + smb2_util_close(tree, *h2); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test durability v2 create replay detection on single channel. + * Variant with leases instead of oplocks, where the + * replay does not specify the original lease level but + * just a "R" lease. This still gives the upgraded lease + * level in the reply. + * - open a file with a rh lease + * - upgrade to a rwh lease with a second create + * - replay the first create. + * ==> it gets back the upgraded lease level + */ +static bool test_replay_dhv2_lease2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io1, io2, ref1; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay2_lease2.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + uint32_t server_capabilities; + struct smb2_lease ls1, ls2; + uint64_t lease_key; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport->conn); + if (!(server_capabilities & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease " + "on Single Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key = random(); + + smb2_lease_create(&io1, &ls1, false /* dir */, fname, + lease_key, smb2_util_lease_state("RH")); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key); + if (share_is_so) { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("R")); + CHECK_VAL(io1.out.durable_open_v2, false); + CHECK_VAL(io1.out.timeout, 0); + } else { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("RH")); + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + } + ref1 = io1; + _h1 = io1.out.file.handle; + h1 = &_h1; + + /* + * Upgrade the lease to RWH + */ + smb2_lease_create(&io2, &ls2, false /* dir */, fname, + lease_key, smb2_util_lease_state("RHW")); + io2.in.durable_open = false; + io2.in.durable_open_v2 = true; + io2.in.persistent_open = false; + io2.in.create_guid = GUID_random(); /* new guid... */ + io2.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io2.out.file.handle; + h2 = &_h2; + + /* + * Replay Durable V2 Create on single channel. + * Changing the requested lease level to "R" + * does not change the response: + * We get the reply from open #1 but with the + * upgraded lease. + */ + + /* adapt the expected response */ + if (!share_is_so) { + ref1.out.lease_response.lease_state = + smb2_util_lease_state("RHW"); + } + + smb2_lease_create(&io1, &ls1, false /* dir */, fname, + lease_key, smb2_util_lease_state("R")); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid; + io1.in.timeout = UINT32_MAX; + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io1); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATE_OUT(&io1, &ref1); + CHECK_VAL(break_info.count, 0); + +done: + smb2cli_session_stop_replay(tree->session->smbXcli); + + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + if (h2 != NULL) { + smb2_util_close(tree, *h2); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test durability v2 create replay detection on single channel. + * create with a lease, and replay with a different lease key + */ +static bool test_replay_dhv2_lease3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io1, io2; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay2_lease2.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + uint32_t server_capabilities; + struct smb2_lease ls1, ls2; + uint64_t lease_key; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport->conn); + if (!(server_capabilities & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease " + "on Single Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key = random(); + + smb2_lease_create(&io1, &ls1, false /* dir */, fname, + lease_key, smb2_util_lease_state("RH")); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key); + if (share_is_so) { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("R")); + CHECK_VAL(io1.out.durable_open_v2, false); + CHECK_VAL(io1.out.timeout, 0); + } else { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("RH")); + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + } + _h1 = io1.out.file.handle; + h1 = &_h1; + + /* + * Upgrade the lease to RWH + */ + smb2_lease_create(&io2, &ls2, false /* dir */, fname, + lease_key, smb2_util_lease_state("RHW")); + io2.in.durable_open = false; + io2.in.durable_open_v2 = true; + io2.in.persistent_open = false; + io2.in.create_guid = GUID_random(); /* new guid... */ + io2.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io2.out.file.handle; + h2 = &_h2; + + /* + * Replay Durable V2 Create on single channel. + * use a different lease key. + */ + + smb2_lease_create(&io1, &ls1, false /* dir */, fname, + random() /* lease key */, + smb2_util_lease_state("RH")); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid; + io1.in.timeout = UINT32_MAX; + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io1); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + +done: + smb2cli_session_stop_replay(tree->session->smbXcli); + + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + if (h2 != NULL) { + smb2_util_close(tree, *h2); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test durability v2 create replay detection on single channel. + * Do the original create with a lease, and do the replay + * with an oplock. + */ +static bool test_replay_dhv2_lease_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io1, io2, ref1; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay2_lease1.dat"; + struct smb2_transport *transport = tree->session->transport; + uint32_t share_capabilities; + bool share_is_so; + uint32_t server_capabilities; + struct smb2_lease ls1, ls2; + uint64_t lease_key; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport->conn); + if (!(server_capabilities & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease " + "on Single Channel\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key = random(); + + smb2_lease_create(&io1, &ls1, false /* dir */, fname, + lease_key, smb2_util_lease_state("RH")); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + ref1 = io1; + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key); + if (share_is_so) { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("R")); + CHECK_VAL(io1.out.durable_open_v2, false); + CHECK_VAL(io1.out.timeout, 0); + } else { + CHECK_VAL(io1.out.lease_response.lease_state, + smb2_util_lease_state("RH")); + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + } + + /* + * Upgrade the lease to RWH + */ + smb2_lease_create(&io2, &ls2, false /* dir */, fname, + lease_key, smb2_util_lease_state("RHW")); + io2.in.durable_open = false; + io2.in.durable_open_v2 = true; + io2.in.persistent_open = false; + io2.in.create_guid = GUID_random(); /* new guid... */ + io2.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io2.out.file.handle; + h2 = &_h2; + + /* + * Replay Durable V2 Create on single channel. + * We get the io from open #1 but with the + * upgraded lease. + */ + + smb2_oplock_create_share(&io2, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io2.in.durable_open = false; + io2.in.durable_open_v2 = true; + io2.in.persistent_open = false; + io2.in.create_guid = create_guid; + io2.in.timeout = UINT32_MAX; + + /* adapt expected lease in response */ + if (!share_is_so) { + ref1.out.lease_response.lease_state = + smb2_util_lease_state("RHW"); + } + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io1); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATE_OUT(&io1, &ref1); + CHECK_VAL(break_info.count, 0); + +done: + smb2cli_session_stop_replay(tree->session->smbXcli); + + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + if (h2 != NULL) { + smb2_util_close(tree, *h2); + } + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * This tests replay with a pending open on a single + * channel. It tests the case where the client2 open + * is deferred because it conflicts with a HANDLE lease, + * which is broken because the operation should otherwise + * return NT_STATUS_SHARING_VIOLATION. + * + * With a durablev2 request containing a create_guid: + * - client2_level = NONE: + * but without asking for an oplock nor a lease. + * - client2_level = BATCH: + * and asking for a batch oplock. + * - client2_level = LEASE + * and asking for an RWH lease. + * + * While another client holds a batch oplock or + * RWH lease. (client1_level => LEASE or BATCH). + * + * There are two modes of this test one, with releaseing + * the oplock/lease of client1 via close or ack. + * (release_op SMB2_OP_CLOSE/SMB2_OP_BREAK). + * + * Windows doesn't detect replays in this case and + * always result in NT_STATUS_SHARING_VIOLATION. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + */ +static bool _test_dhv2_pending1_vs_violation(struct torture_context *tctx, + const char *testname, + struct smb2_tree *tree1, + uint8_t client1_level, + uint8_t release_op, + struct smb2_tree *tree2, + uint8_t client2_level, + NTSTATUS orig21_reject_status, + NTSTATUS replay22_reject_status, + NTSTATUS replay23_reject_status) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle *h2f = NULL; + struct smb2_handle _h21; + struct smb2_handle *h21 = NULL; + struct smb2_handle _h23; + struct smb2_handle *h23 = NULL; + struct smb2_handle _h24; + struct smb2_handle *h24 = NULL; + struct smb2_create io1, io21, io22, io23, io24; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_request *req21 = NULL; + struct smb2_request *req22 = NULL; + bool ret = true; + char fname[256]; + struct smb2_transport *transport1 = tree1->session->transport; + uint32_t server_capabilities; + uint32_t share_capabilities; + struct smb2_lease ls1; + uint64_t lease_key1; + uint16_t lease_epoch1 = 0; + struct smb2_break op_ack1; + struct smb2_lease_break_ack lb_ack1; + struct smb2_lease ls2; + uint64_t lease_key2; + uint16_t lease_epoch2 = 0; + bool share_is_so; + struct smb2_transport *transport2 = tree2->session->transport; + int request_timeout2 = transport2->options.request_timeout; + struct smb2_session *session2 = tree2->session; + const char *hold_name = NULL; + + switch (client1_level) { + case SMB2_OPLOCK_LEVEL_LEASE: + hold_name = "RWH Lease"; + break; + case SMB2_OPLOCK_LEVEL_BATCH: + hold_name = "BATCH Oplock"; + break; + default: + smb_panic(__location__); + break; + } + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport1->conn); + if (!(server_capabilities & SMB2_CAP_LEASING)) { + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE || + client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_skip(tctx, "leases are not supported"); + } + } + + share_capabilities = smb2cli_tcon_capabilities(tree1->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + if (share_is_so) { + torture_skip(tctx, talloc_asprintf(tctx, + "%s not supported on SCALEOUT share", + hold_name)); + } + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "%s\\%s_%s.dat", + BASEDIR, testname, generate_random_str(tctx, 8)); + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + ZERO_STRUCT(op_ack1); + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + ZERO_STRUCT(lb_ack1); + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + smb2_keepalive(transport1); + transport2->oplock.handler = torture_oplock_ack_handler; + transport2->oplock.private_data = tree2; + transport2->lease.handler = torture_lease_handler; + transport2->lease.private_data = tree2; + smb2_keepalive(transport2); + + smb2_util_unlink(tree1, fname); + status = torture_smb2_testdir(tree1, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key1 = random(); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io1, &ls1, false /* dir */, fname, + lease_key1, NULL, smb2_util_lease_state("RWH"), lease_epoch1++); + } else { + smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH); + } + io1.in.share_access = 0; + io1.in.desired_access = SEC_RIGHTS_FILE_ALL; + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid1; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[0], lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[1], ~lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_epoch, lease_epoch1); + CHECK_VAL(io1.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH")); + } else { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + } + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + + lease_key2 = random(); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io21, &ls2, false /* dir */, fname, + lease_key2, NULL, smb2_util_lease_state("RWH"), lease_epoch2++); + } else { + smb2_oplock_create(&io21, fname, client2_level); + } + io21.in.share_access = 0; + io21.in.desired_access = SEC_RIGHTS_FILE_ALL; + io21.in.desired_access = SEC_RIGHTS_FILE_READ; + io21.in.durable_open = false; + io21.in.durable_open_v2 = true; + io21.in.persistent_open = false; + io21.in.create_guid = create_guid2; + io21.in.timeout = UINT32_MAX; + io24 = io23 = io22 = io21; + + req21 = smb2_create_send(tree2, &io21); + torture_assert(tctx, req21 != NULL, "req21"); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + const struct smb2_lease_break *lb = + &lease_break_info.lease_break; + const struct smb2_lease *l = &lb->current_lease; + const struct smb2_lease_key *k = &l->lease_key; + + torture_wait_for_lease_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 1); + + torture_assert(tctx, + lease_break_info.lease_transport == transport1, + "expect lease break on transport1\n"); + CHECK_VAL(k->data[0], lease_key1); + CHECK_VAL(k->data[1], ~lease_key1); + /* + * With share none the handle lease + * is broken. + */ + CHECK_VAL(lb->new_lease_state, + smb2_util_lease_state("RW")); + CHECK_VAL(lb->break_flags, + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); + CHECK_VAL(lb->new_epoch, lease_epoch1+1); + lease_epoch1 += 1; + + lb_ack1.in.lease.lease_key = lb->current_lease.lease_key; + lb_ack1.in.lease.lease_state = lb->new_lease_state; + } else { + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(lease_break_info.count, 0); + + torture_assert(tctx, + break_info.received_transport == transport1, + "expect oplock break on transport1\n"); + CHECK_VAL(break_info.handle.data[0], _h1.data[0]); + CHECK_VAL(break_info.handle.data[1], _h1.data[1]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + + op_ack1.in = break_info.br.in; + } + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + WAIT_FOR_ASYNC_RESPONSE(tctx, req21); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + if (NT_STATUS_EQUAL(replay22_reject_status, NT_STATUS_SHARING_VIOLATION)) { + /* + * The server is broken and doesn't + * detect a replay, so we start an async + * request and send a lease break ack + * after 5 seconds in order to avoid + * the 35 second delay. + */ + torture_comment(tctx, "Starting ASYNC Replay req22 expecting %s\n", + nt_errstr(replay22_reject_status)); + smb2cli_session_start_replay(session2->smbXcli); + transport2->options.request_timeout = 15; + req22 = smb2_create_send(tree2, &io22); + torture_assert(tctx, req22 != NULL, "req22"); + transport2->options.request_timeout = request_timeout2; + smb2cli_session_stop_replay(session2->smbXcli); + + WAIT_FOR_ASYNC_RESPONSE(tctx, req22); + } else { + torture_comment(tctx, "SYNC Replay io22 expecting %s\n", + nt_errstr(replay22_reject_status)); + smb2cli_session_start_replay(session2->smbXcli); + transport2->options.request_timeout = 5; + status = smb2_create(tree2, tctx, &io22); + CHECK_STATUS(status, replay22_reject_status); + transport2->options.request_timeout = request_timeout2; + smb2cli_session_stop_replay(session2->smbXcli); + } + + /* + * We don't expect any action for 35 seconds + * + * But we sleep just 5 seconds before we + * ack the break. + */ + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + torture_wait_for_lease_break(tctx); + torture_wait_for_lease_break(tctx); + torture_wait_for_lease_break(tctx); + torture_wait_for_lease_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + if (release_op == SMB2_OP_CLOSE) { + torture_comment(tctx, "Closing h1\n"); + smb2_util_close(tree1, _h1); + h1 = NULL; + } else { + torture_comment(tctx, "Acking lease_key1\n"); + status = smb2_lease_break_ack(tree1, &lb_ack1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(lb_ack1.out.lease.lease_flags, 0); + CHECK_VAL(lb_ack1.out.lease.lease_state, lb_ack1.in.lease.lease_state); + CHECK_VAL(lb_ack1.out.lease.lease_key.data[0], lease_key1); + CHECK_VAL(lb_ack1.out.lease.lease_key.data[1], ~lease_key1); + CHECK_VAL(lb_ack1.out.lease.lease_duration, 0); + } + } else { + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + if (release_op == SMB2_OP_CLOSE) { + torture_comment(tctx, "Closing h1\n"); + smb2_util_close(tree1, _h1); + h1 = NULL; + } else { + torture_comment(tctx, "Acking break h1\n"); + status = smb2_break(tree1, &op_ack1); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(op_ack1.out.oplock_level, op_ack1.in.oplock_level); + } + } + + torture_comment(tctx, "Checking req21 expecting %s\n", + nt_errstr(orig21_reject_status)); + status = smb2_create_recv(req21, tctx, &io21); + CHECK_STATUS(status, orig21_reject_status); + if (NT_STATUS_IS_OK(orig21_reject_status)) { + _h21 = io21.out.file.handle; + h21 = &_h21; + if (h2f == NULL) { + h2f = h21; + } + CHECK_VAL(h21->data[0], h2f->data[0]); + CHECK_VAL(h21->data[1], h2f->data[1]); + CHECK_CREATED(&io21, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io21.out.oplock_level, client2_level); + CHECK_VAL(io21.out.durable_open, false); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io21.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io21.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io21.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io21.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io21.out.durable_open_v2, true); + CHECK_VAL(io21.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io21.out.durable_open_v2, true); + CHECK_VAL(io21.out.timeout, 300*1000); + } else { + CHECK_VAL(io21.out.durable_open_v2, false); + } + } + + if (NT_STATUS_EQUAL(replay22_reject_status, NT_STATUS_SHARING_VIOLATION)) { + torture_comment(tctx, "Checking req22 expecting %s\n", + nt_errstr(replay22_reject_status)); + status = smb2_create_recv(req22, tctx, &io22); + CHECK_STATUS(status, replay22_reject_status); + } + + torture_comment(tctx, "SYNC Replay io23 expecting %s\n", + nt_errstr(replay23_reject_status)); + smb2cli_session_start_replay(session2->smbXcli); + transport2->options.request_timeout = 5; + status = smb2_create(tree2, tctx, &io23); + transport2->options.request_timeout = request_timeout2; + CHECK_STATUS(status, replay23_reject_status); + smb2cli_session_stop_replay(session2->smbXcli); + if (NT_STATUS_IS_OK(replay23_reject_status)) { + _h23 = io23.out.file.handle; + h23 = &_h23; + if (h2f == NULL) { + h2f = h23; + } + CHECK_VAL(h23->data[0], h2f->data[0]); + CHECK_VAL(h23->data[1], h2f->data[1]); + CHECK_CREATED(&io23, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io23.out.oplock_level, client2_level); + CHECK_VAL(io23.out.durable_open, false); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io23.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io23.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io23.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io23.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io23.out.durable_open_v2, true); + CHECK_VAL(io23.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io23.out.durable_open_v2, true); + CHECK_VAL(io23.out.timeout, 300*1000); + } else { + CHECK_VAL(io23.out.durable_open_v2, false); + } + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + if (h1 != NULL) { + torture_comment(tctx, "Closing h1\n"); + smb2_util_close(tree1, _h1); + h1 = NULL; + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + torture_comment(tctx, "SYNC Replay io24 expecting %s\n", + nt_errstr(NT_STATUS_OK)); + smb2cli_session_start_replay(session2->smbXcli); + transport2->options.request_timeout = 5; + status = smb2_create(tree2, tctx, &io24); + transport2->options.request_timeout = request_timeout2; + smb2cli_session_stop_replay(session2->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + _h24 = io24.out.file.handle; + h24 = &_h24; + if (h2f == NULL) { + h2f = h24; + } + CHECK_VAL(h24->data[0], h2f->data[0]); + CHECK_VAL(h24->data[1], h2f->data[1]); + CHECK_CREATED(&io24, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io24.out.oplock_level, client2_level); + CHECK_VAL(io24.out.durable_open, false); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io24.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else { + CHECK_VAL(io24.out.durable_open_v2, false); + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + status = smb2_util_close(tree2, *h24); + CHECK_STATUS(status, NT_STATUS_OK); + h24 = NULL; + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + +done: + + smbXcli_conn_disconnect(transport2->conn, NT_STATUS_LOCAL_DISCONNECT); + + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + + smb2_deltree(tree1, BASEDIR); + + TALLOC_FREE(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/* + * This tests replay with a pending open on a single + * channel. It tests the case where the client2 open + * is deferred because it conflicts with a HANDLE lease, + * which is broken because the operation should otherwise + * return NT_STATUS_SHARING_VIOLATION. + * + * With a durablev2 request containing a create_guid, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease, + * which is released by a close. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_SHARING_VIOLATION to the replay (after + * 35 seconds), and this tests reports NT_STATUS_IO_TIMEOUT, + * as it expectes a NT_STATUS_FILE_NOT_AVAILABLE within 5 seconds. + * see test_dhv2_pending1n_vs_violation_lease_close_windows(). + */ +static bool test_dhv2_pending1n_vs_violation_lease_close_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_violation(tctx, __func__, + tree1, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OP_CLOSE, + tree2, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_OK, + NT_STATUS_FILE_NOT_AVAILABLE, + NT_STATUS_OK); +} + +/* + * This tests replay with a pending open on a single + * channel. It tests the case where the client2 open + * is deferred because it conflicts with a HANDLE lease, + * which is broken because the operation should otherwise + * return NT_STATUS_SHARING_VIOLATION. + * + * With a durablev2 request containing a create_guid, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease, + * which is released by a close. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange behavior of ignoring the + * replay, which is returned done by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE + * see test_dhv2_pending1n_vs_violation_lease_close_sane(). + */ +static bool test_dhv2_pending1n_vs_violation_lease_close_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_violation(tctx, __func__, + tree1, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OP_CLOSE, + tree2, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_OK, + NT_STATUS_SHARING_VIOLATION, + NT_STATUS_OK); +} + +/* + * This tests replay with a pending open on a single + * channel. It tests the case where the client2 open + * is deferred because it conflicts with a HANDLE lease, + * which is broken because the operation should otherwise + * return NT_STATUS_SHARING_VIOLATION. + * + * With a durablev2 request containing a create_guid, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease, + * which is released by a lease break ack. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_SHARING_VIOLATION to the replay (after + * 35 seconds), and this tests reports NT_STATUS_IO_TIMEOUT, + * as it expectes a NT_STATUS_FILE_NOT_AVAILABLE within 5 seconds. + * see test_dhv2_pending1n_vs_violation_lease_ack_windows(). + */ +static bool test_dhv2_pending1n_vs_violation_lease_ack_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_violation(tctx, __func__, + tree1, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OP_BREAK, + tree2, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_SHARING_VIOLATION, + NT_STATUS_FILE_NOT_AVAILABLE, + NT_STATUS_SHARING_VIOLATION); +} + +/* + * This tests replay with a pending open on a single + * channel. It tests the case where the client2 open + * is deferred because it conflicts with a HANDLE lease, + * which is broken because the operation should otherwise + * return NT_STATUS_SHARING_VIOLATION. + * + * With a durablev2 request containing a create_guid, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease, + * which is released by a close. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange behavior of ignoring the + * replay, which is returned done by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE + * see test_dhv2_pending1n_vs_violation_lease_ack_sane(). + */ +static bool test_dhv2_pending1n_vs_violation_lease_ack_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_violation(tctx, __func__, + tree1, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OP_BREAK, + tree2, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_SHARING_VIOLATION, + NT_STATUS_SHARING_VIOLATION, + NT_STATUS_SHARING_VIOLATION); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid and + * a share_access of READ/WRITE/DELETE: + * - client2_level = NONE: + * but without asking for an oplock nor a lease. + * - client2_level = BATCH: + * and asking for a batch oplock. + * - client2_level = LEASE + * and asking for an RWH lease. + * + * While another client holds a batch oplock or + * RWH lease. (client1_level => LEASE or BATCH). + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + */ +static bool _test_dhv2_pending1_vs_hold(struct torture_context *tctx, + const char *testname, + uint8_t client1_level, + uint8_t client2_level, + NTSTATUS reject_status, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h21; + struct smb2_handle *h21 = NULL; + struct smb2_handle _h24; + struct smb2_handle *h24 = NULL; + struct smb2_create io1, io21, io22, io23, io24; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_request *req21 = NULL; + bool ret = true; + char fname[256]; + struct smb2_transport *transport1 = tree1->session->transport; + uint32_t server_capabilities; + uint32_t share_capabilities; + struct smb2_lease ls1; + uint64_t lease_key1; + uint16_t lease_epoch1 = 0; + struct smb2_lease ls2; + uint64_t lease_key2; + uint16_t lease_epoch2 = 0; + bool share_is_so; + struct smb2_transport *transport2 = tree2->session->transport; + int request_timeout2 = transport2->options.request_timeout; + struct smb2_session *session2 = tree2->session; + const char *hold_name = NULL; + + switch (client1_level) { + case SMB2_OPLOCK_LEVEL_LEASE: + hold_name = "RWH Lease"; + break; + case SMB2_OPLOCK_LEVEL_BATCH: + hold_name = "BATCH Oplock"; + break; + default: + smb_panic(__location__); + break; + } + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport1->conn); + if (!(server_capabilities & SMB2_CAP_LEASING)) { + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE || + client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_skip(tctx, "leases are not supported"); + } + } + + share_capabilities = smb2cli_tcon_capabilities(tree1->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + if (share_is_so) { + torture_skip(tctx, talloc_asprintf(tctx, + "%s not supported on SCALEOUT share", + hold_name)); + } + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "%s\\%s_%s.dat", + BASEDIR, testname, generate_random_str(tctx, 8)); + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + smb2_keepalive(transport1); + transport2->oplock.handler = torture_oplock_ack_handler; + transport2->oplock.private_data = tree2; + transport2->lease.handler = torture_lease_handler; + transport2->lease.private_data = tree2; + smb2_keepalive(transport2); + + smb2_util_unlink(tree1, fname); + status = torture_smb2_testdir(tree1, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key1 = random(); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io1, &ls1, false /* dir */, fname, + lease_key1, NULL, smb2_util_lease_state("RWH"), lease_epoch1++); + } else { + smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH); + } + io1.in.share_access = smb2_util_share_access("RWD"); + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid1; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[0], lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[1], ~lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_epoch, lease_epoch1); + CHECK_VAL(io1.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + } else { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + } + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + + lease_key2 = random(); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io21, &ls2, false /* dir */, fname, + lease_key2, NULL, smb2_util_lease_state("RWH"), lease_epoch2++); + } else { + smb2_oplock_create(&io21, fname, client2_level); + } + io21.in.share_access = smb2_util_share_access("RWD"); + io21.in.durable_open = false; + io21.in.durable_open_v2 = true; + io21.in.persistent_open = false; + io21.in.create_guid = create_guid2; + io21.in.timeout = UINT32_MAX; + io24 = io23 = io22 = io21; + + req21 = smb2_create_send(tree2, &io21); + torture_assert(tctx, req21 != NULL, "req21"); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + const struct smb2_lease_break *lb = + &lease_break_info.lease_break; + const struct smb2_lease *l = &lb->current_lease; + const struct smb2_lease_key *k = &l->lease_key; + + torture_wait_for_lease_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 1); + + torture_assert(tctx, + lease_break_info.lease_transport == transport1, + "expect lease break on transport1\n"); + CHECK_VAL(k->data[0], lease_key1); + CHECK_VAL(k->data[1], ~lease_key1); + CHECK_VAL(lb->new_lease_state, + smb2_util_lease_state("RH")); + CHECK_VAL(lb->break_flags, + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); + CHECK_VAL(lb->new_epoch, lease_epoch1+1); + lease_epoch1 += 1; + } else { + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(lease_break_info.count, 0); + + torture_assert(tctx, + break_info.received_transport == transport1, + "expect oplock break on transport1\n"); + CHECK_VAL(break_info.handle.data[0], _h1.data[0]); + CHECK_VAL(break_info.handle.data[1], _h1.data[1]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + } + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + WAIT_FOR_ASYNC_RESPONSE(tctx, req21); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smb2cli_session_start_replay(session2->smbXcli); + transport2->options.request_timeout = 5; + status = smb2_create(tree2, tctx, &io22); + transport2->options.request_timeout = request_timeout2; + CHECK_STATUS(status, reject_status); + smb2cli_session_stop_replay(session2->smbXcli); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smb2cli_session_start_replay(session2->smbXcli); + transport2->options.request_timeout = 5; + status = smb2_create(tree2, tctx, &io23); + transport2->options.request_timeout = request_timeout2; + CHECK_STATUS(status, reject_status); + smb2cli_session_stop_replay(session2->smbXcli); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smb2_util_close(tree1, _h1); + h1 = NULL; + + status = smb2_create_recv(req21, tctx, &io21); + CHECK_STATUS(status, NT_STATUS_OK); + _h21 = io21.out.file.handle; + h21 = &_h21; + CHECK_CREATED(&io21, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io21.out.oplock_level, client2_level); + CHECK_VAL(io21.out.durable_open, false); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io21.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io21.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io21.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io21.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io21.out.durable_open_v2, true); + CHECK_VAL(io21.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io21.out.durable_open_v2, true); + CHECK_VAL(io21.out.timeout, 300*1000); + } else { + CHECK_VAL(io21.out.durable_open_v2, false); + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smb2cli_session_start_replay(session2->smbXcli); + status = smb2_create(tree2, tctx, &io24); + smb2cli_session_stop_replay(session2->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + _h24 = io24.out.file.handle; + h24 = &_h24; + CHECK_CREATED(&io24, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(h24->data[0], h21->data[0]); + CHECK_VAL(h24->data[1], h21->data[1]); + CHECK_VAL(io24.out.oplock_level, client2_level); + CHECK_VAL(io24.out.durable_open, false); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io24.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else { + CHECK_VAL(io24.out.durable_open_v2, false); + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + status = smb2_util_close(tree2, *h24); + CHECK_STATUS(status, NT_STATUS_OK); + h24 = NULL; + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + +done: + + smbXcli_conn_disconnect(transport2->conn, NT_STATUS_LOCAL_DISCONNECT); + + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + + smb2_deltree(tree1, BASEDIR); + + TALLOC_FREE(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending1n_vs_oplock_windows(). + */ +static bool test_dhv2_pending1n_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending1n_vs_oplock_sane. + */ +static bool test_dhv2_pending1n_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending1n_vs_lease_windows(). + */ +static bool test_dhv2_pending1n_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending1n_vs_lease_sane. + */ +static bool test_dhv2_pending1n_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending1l_vs_oplock_windows(). + */ +static bool test_dhv2_pending1l_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending1l_vs_oplock_sane. + */ +static bool test_dhv2_pending1l_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending1l_vs_lease_windows(). + */ +static bool test_dhv2_pending1l_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending1l_vs_lease_sane. + */ +static bool test_dhv2_pending1l_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending1o_vs_oplock_windows(). + */ +static bool test_dhv2_pending1o_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending1o_vs_oplock_sane. + */ +static bool test_dhv2_pending1o_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_ACCESS_DENIED, + tree1, tree2); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending1o_vs_lease_windows(). + */ +static bool test_dhv2_pending1o_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open on a single + * channel. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending1o_vs_lease_sane. + */ +static bool test_dhv2_pending1o_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + return _test_dhv2_pending1_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_ACCESS_DENIED, + tree1, tree2); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid and + * a share_access of READ/WRITE/DELETE: + * - client2_level = NONE: + * but without asking for an oplock nor a lease. + * - client2_level = BATCH: + * and asking for a batch oplock. + * - client2_level = LEASE + * and asking for an RWH lease. + * + * While another client holds a batch oplock or + * RWH lease. (client1_level => LEASE or BATCH). + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + */ +static bool _test_dhv2_pending2_vs_hold(struct torture_context *tctx, + const char *testname, + uint8_t client1_level, + uint8_t client2_level, + NTSTATUS reject_status, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h24; + struct smb2_handle *h24 = NULL; + struct smb2_create io1, io21, io22, io23, io24; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_request *req21 = NULL; + bool ret = true; + char fname[256]; + struct smb2_transport *transport1 = tree1->session->transport; + uint32_t server_capabilities; + uint32_t share_capabilities; + struct smb2_lease ls1; + uint64_t lease_key1; + uint16_t lease_epoch1 = 0; + struct smb2_lease ls2; + uint64_t lease_key2; + uint16_t lease_epoch2 = 0; + bool share_is_so; + struct smb2_transport *transport2_1 = tree2_1->session->transport; + int request_timeout2 = transport2_1->options.request_timeout; + struct smbcli_options options2x; + struct smb2_tree *tree2_2 = NULL; + struct smb2_tree *tree2_3 = NULL; + struct smb2_tree *tree2_4 = NULL; + struct smb2_transport *transport2_2 = NULL; + struct smb2_transport *transport2_3 = NULL; + struct smb2_transport *transport2_4 = NULL; + struct smb2_session *session2_1 = tree2_1->session; + struct smb2_session *session2_2 = NULL; + struct smb2_session *session2_3 = NULL; + struct smb2_session *session2_4 = NULL; + uint16_t csn2 = 1; + const char *hold_name = NULL; + + switch (client1_level) { + case SMB2_OPLOCK_LEVEL_LEASE: + hold_name = "RWH Lease"; + break; + case SMB2_OPLOCK_LEVEL_BATCH: + hold_name = "BATCH Oplock"; + break; + default: + smb_panic(__location__); + break; + } + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport1->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, "MULTI_CHANNEL are not supported"); + } + if (!(server_capabilities & SMB2_CAP_LEASING)) { + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE || + client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_skip(tctx, "leases are not supported"); + } + } + + share_capabilities = smb2cli_tcon_capabilities(tree1->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + if (share_is_so) { + torture_skip(tctx, talloc_asprintf(tctx, + "%s not supported on SCALEOUT share", + hold_name)); + } + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "%s\\%s_%s.dat", + BASEDIR, testname, generate_random_str(tctx, 8)); + + options2x = transport2_1->options; + options2x.only_negprot = true; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2_2, + tctx->ev, + &options2x, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2_2 = tree2_2->session->transport; + + session2_2 = smb2_session_channel(transport2_2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx, + session2_1); + torture_assert(tctx, session2_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_2, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + tree2_2->smbXcli = tree2_1->smbXcli; + tree2_2->session = session2_2; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2_3, + tctx->ev, + &options2x, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2_3 = tree2_3->session->transport; + + session2_3 = smb2_session_channel(transport2_3, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx, + session2_1); + torture_assert(tctx, session2_3 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_3, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + tree2_3->smbXcli = tree2_1->smbXcli; + tree2_3->session = session2_3; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2_4, + tctx->ev, + &options2x, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2_4 = tree2_4->session->transport; + + session2_4 = smb2_session_channel(transport2_4, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx, + session2_1); + torture_assert(tctx, session2_4 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_4, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + tree2_4->smbXcli = tree2_1->smbXcli; + tree2_4->session = session2_4; + + smb2cli_session_reset_channel_sequence(session2_2->smbXcli, csn2++); + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + smb2_keepalive(transport1); + transport2_1->oplock.handler = torture_oplock_ack_handler; + transport2_1->oplock.private_data = tree2_1; + transport2_1->lease.handler = torture_lease_handler; + transport2_1->lease.private_data = tree2_1; + smb2_keepalive(transport2_1); + transport2_2->oplock.handler = torture_oplock_ack_handler; + transport2_2->oplock.private_data = tree2_2; + transport2_2->lease.handler = torture_lease_handler; + transport2_2->lease.private_data = tree2_2; + smb2_keepalive(transport2_2); + transport2_3->oplock.handler = torture_oplock_ack_handler; + transport2_3->oplock.private_data = tree2_3; + transport2_3->lease.handler = torture_lease_handler; + transport2_3->lease.private_data = tree2_3; + smb2_keepalive(transport2_3); + transport2_4->oplock.handler = torture_oplock_ack_handler; + transport2_4->oplock.private_data = tree2_4; + transport2_4->lease.handler = torture_lease_handler; + transport2_4->lease.private_data = tree2_4; + smb2_keepalive(transport2_4); + + smb2_util_unlink(tree1, fname); + status = torture_smb2_testdir(tree1, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key1 = random(); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io1, &ls1, false /* dir */, fname, + lease_key1, NULL, smb2_util_lease_state("RWH"), lease_epoch1++); + } else { + smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH); + } + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid1; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[0], lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[1], ~lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_epoch, lease_epoch1); + CHECK_VAL(io1.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + } else { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + } + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + + lease_key2 = random(); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io21, &ls2, false /* dir */, fname, + lease_key2, NULL, smb2_util_lease_state("RWH"), lease_epoch2++); + } else { + smb2_oplock_create(&io21, fname, client2_level); + } + io21.in.durable_open = false; + io21.in.durable_open_v2 = true; + io21.in.persistent_open = false; + io21.in.create_guid = create_guid2; + io21.in.timeout = UINT32_MAX; + io24 = io23 = io22 = io21; + + req21 = smb2_create_send(tree2_1, &io21); + torture_assert(tctx, req21 != NULL, "req21"); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + const struct smb2_lease_break *lb = + &lease_break_info.lease_break; + const struct smb2_lease *l = &lb->current_lease; + const struct smb2_lease_key *k = &l->lease_key; + + torture_wait_for_lease_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 1); + + torture_assert(tctx, + lease_break_info.lease_transport == transport1, + "expect lease break on transport1\n"); + CHECK_VAL(k->data[0], lease_key1); + CHECK_VAL(k->data[1], ~lease_key1); + CHECK_VAL(lb->new_lease_state, + smb2_util_lease_state("RH")); + CHECK_VAL(lb->break_flags, + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); + CHECK_VAL(lb->new_epoch, lease_epoch1+1); + lease_epoch1 += 1; + } else { + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(lease_break_info.count, 0); + + torture_assert(tctx, + break_info.received_transport == transport1, + "expect oplock break on transport1\n"); + CHECK_VAL(break_info.handle.data[0], _h1.data[0]); + CHECK_VAL(break_info.handle.data[1], _h1.data[1]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + } + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + WAIT_FOR_ASYNC_RESPONSE(tctx, req21); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smbXcli_conn_disconnect(transport2_1->conn, NT_STATUS_LOCAL_DISCONNECT); + smb2cli_session_reset_channel_sequence(session2_1->smbXcli, csn2++); + + smb2cli_session_start_replay(session2_2->smbXcli); + transport2_2->options.request_timeout = 5; + status = smb2_create(tree2_2, tctx, &io22); + transport2_2->options.request_timeout = request_timeout2; + CHECK_STATUS(status, reject_status); + smb2cli_session_stop_replay(session2_2->smbXcli); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smbXcli_conn_disconnect(transport2_2->conn, NT_STATUS_LOCAL_DISCONNECT); + smb2cli_session_reset_channel_sequence(session2_2->smbXcli, csn2++); + + smb2cli_session_start_replay(session2_3->smbXcli); + transport2_3->options.request_timeout = 5; + status = smb2_create(tree2_3, tctx, &io23); + transport2_3->options.request_timeout = request_timeout2; + CHECK_STATUS(status, reject_status); + smb2cli_session_stop_replay(session2_3->smbXcli); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smb2_util_close(tree1, _h1); + h1 = NULL; + + status = smb2_create_recv(req21, tctx, &io21); + CHECK_STATUS(status, NT_STATUS_LOCAL_DISCONNECT); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smbXcli_conn_disconnect(transport2_3->conn, NT_STATUS_LOCAL_DISCONNECT); + smb2cli_session_reset_channel_sequence(session2_3->smbXcli, csn2++); + + smb2cli_session_start_replay(session2_4->smbXcli); + status = smb2_create(tree2_4, tctx, &io24); + smb2cli_session_stop_replay(session2_4->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + _h24 = io24.out.file.handle; + h24 = &_h24; + CHECK_CREATED(&io24, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io24.out.oplock_level, client2_level); + CHECK_VAL(io24.out.durable_open, false); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io24.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else { + CHECK_VAL(io24.out.durable_open_v2, false); + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + status = smb2_util_close(tree2_4, *h24); + CHECK_STATUS(status, NT_STATUS_OK); + h24 = NULL; + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + +done: + + smbXcli_conn_disconnect(transport2_1->conn, NT_STATUS_LOCAL_DISCONNECT); + smbXcli_conn_disconnect(transport2_2->conn, NT_STATUS_LOCAL_DISCONNECT); + smbXcli_conn_disconnect(transport2_3->conn, NT_STATUS_LOCAL_DISCONNECT); + smbXcli_conn_disconnect(transport2_4->conn, NT_STATUS_LOCAL_DISCONNECT); + + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + + smb2_deltree(tree1, BASEDIR); + + TALLOC_FREE(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending2n_vs_lease_windows(). + */ +static bool test_dhv2_pending2n_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending2n_vs_lease_sane(). + */ +static bool test_dhv2_pending2n_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending2n_vs_oplock_windows(). + */ +static bool test_dhv2_pending2n_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending2n_vs_oplock_sane(). + */ +static bool test_dhv2_pending2n_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending2l_vs_oplock_windows(). + */ +static bool test_dhv2_pending2l_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending2l_vs_oplock_sane(). + */ +static bool test_dhv2_pending2l_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending2l_vs_oplock_windows(). + */ +static bool test_dhv2_pending2l_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending2l_vs_oplock_sane(). + */ +static bool test_dhv2_pending2l_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending2o_vs_oplock_windows(). + */ +static bool test_dhv2_pending2o_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending2o_vs_oplock_sane(). + */ +static bool test_dhv2_pending2o_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending2o_vs_lease_windows(). + */ +static bool test_dhv2_pending2o_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and closed transports on the client and server side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending2o_vs_lease_sane(). + */ +static bool test_dhv2_pending2o_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending2_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid and + * a share_access of READ/WRITE/DELETE: + * - client2_level = NONE: + * but without asking for an oplock nor a lease. + * - client2_level = BATCH: + * and asking for a batch oplock. + * - client2_level = LEASE + * and asking for an RWH lease. + * + * While another client holds a batch oplock or + * RWH lease. (client1_level => LEASE or BATCH). + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + */ +static bool _test_dhv2_pending3_vs_hold(struct torture_context *tctx, + const char *testname, + uint8_t client1_level, + uint8_t client2_level, + NTSTATUS reject_status, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h21; + struct smb2_handle *h21 = NULL; + struct smb2_handle _h24; + struct smb2_handle *h24 = NULL; + struct smb2_create io1, io21, io22, io23, io24; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_request *req21 = NULL; + bool ret = true; + char fname[256]; + struct smb2_transport *transport1 = tree1->session->transport; + uint32_t server_capabilities; + uint32_t share_capabilities; + struct smb2_lease ls1; + uint64_t lease_key1; + uint16_t lease_epoch1 = 0; + struct smb2_lease ls2; + uint64_t lease_key2; + uint16_t lease_epoch2 = 0; + bool share_is_so; + struct smb2_transport *transport2_1 = tree2_1->session->transport; + int request_timeout2 = transport2_1->options.request_timeout; + struct smbcli_options options2x; + struct smb2_tree *tree2_2 = NULL; + struct smb2_tree *tree2_3 = NULL; + struct smb2_tree *tree2_4 = NULL; + struct smb2_transport *transport2_2 = NULL; + struct smb2_transport *transport2_3 = NULL; + struct smb2_transport *transport2_4 = NULL; + struct smb2_session *session2_1 = tree2_1->session; + struct smb2_session *session2_2 = NULL; + struct smb2_session *session2_3 = NULL; + struct smb2_session *session2_4 = NULL; + bool block_setup = false; + bool blocked2_1 = false; + bool blocked2_2 = false; + bool blocked2_3 = false; + uint16_t csn2 = 1; + const char *hold_name = NULL; + + switch (client1_level) { + case SMB2_OPLOCK_LEVEL_LEASE: + hold_name = "RWH Lease"; + break; + case SMB2_OPLOCK_LEVEL_BATCH: + hold_name = "BATCH Oplock"; + break; + default: + smb_panic(__location__); + break; + } + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities(transport1->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, "MULTI_CHANNEL are not supported"); + } + if (!(server_capabilities & SMB2_CAP_LEASING)) { + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE || + client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_skip(tctx, "leases are not supported"); + } + } + + share_capabilities = smb2cli_tcon_capabilities(tree1->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + if (share_is_so) { + torture_skip(tctx, talloc_asprintf(tctx, + "%s not supported on SCALEOUT share", + hold_name)); + } + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "%s\\%s_%s.dat", + BASEDIR, testname, generate_random_str(tctx, 8)); + + options2x = transport2_1->options; + options2x.only_negprot = true; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2_2, + tctx->ev, + &options2x, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2_2 = tree2_2->session->transport; + + session2_2 = smb2_session_channel(transport2_2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx, + session2_1); + torture_assert(tctx, session2_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_2, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + tree2_2->smbXcli = tree2_1->smbXcli; + tree2_2->session = session2_2; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2_3, + tctx->ev, + &options2x, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2_3 = tree2_3->session->transport; + + session2_3 = smb2_session_channel(transport2_3, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx, + session2_1); + torture_assert(tctx, session2_3 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_3, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + tree2_3->smbXcli = tree2_1->smbXcli; + tree2_3->session = session2_3; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2_4, + tctx->ev, + &options2x, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2_4 = tree2_4->session->transport; + + session2_4 = smb2_session_channel(transport2_4, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx, + session2_1); + torture_assert(tctx, session2_4 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_4, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + tree2_4->smbXcli = tree2_1->smbXcli; + tree2_4->session = session2_4; + + smb2cli_session_reset_channel_sequence(session2_2->smbXcli, csn2++); + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + transport1->lease.handler = torture_lease_handler; + transport1->lease.private_data = tree1; + smb2_keepalive(transport1); + transport2_1->oplock.handler = torture_oplock_ack_handler; + transport2_1->oplock.private_data = tree2_1; + transport2_1->lease.handler = torture_lease_handler; + transport2_1->lease.private_data = tree2_1; + smb2_keepalive(transport2_1); + transport2_2->oplock.handler = torture_oplock_ack_handler; + transport2_2->oplock.private_data = tree2_2; + transport2_2->lease.handler = torture_lease_handler; + transport2_2->lease.private_data = tree2_2; + smb2_keepalive(transport2_2); + transport2_3->oplock.handler = torture_oplock_ack_handler; + transport2_3->oplock.private_data = tree2_3; + transport2_3->lease.handler = torture_lease_handler; + transport2_3->lease.private_data = tree2_3; + smb2_keepalive(transport2_3); + transport2_4->oplock.handler = torture_oplock_ack_handler; + transport2_4->oplock.private_data = tree2_4; + transport2_4->lease.handler = torture_lease_handler; + transport2_4->lease.private_data = tree2_4; + smb2_keepalive(transport2_4); + + smb2_util_unlink(tree1, fname); + status = torture_smb2_testdir(tree1, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h1); + CHECK_VAL(break_info.count, 0); + + lease_key1 = random(); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io1, &ls1, false /* dir */, fname, + lease_key1, NULL, smb2_util_lease_state("RWH"), lease_epoch1++); + } else { + smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH); + } + io1.in.durable_open = false; + io1.in.durable_open_v2 = true; + io1.in.persistent_open = false; + io1.in.create_guid = create_guid1; + io1.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.durable_open, false); + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[0], lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_key.data[1], ~lease_key1); + CHECK_VAL(io1.out.lease_response_v2.lease_epoch, lease_epoch1); + CHECK_VAL(io1.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + } else { + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); + } + CHECK_VAL(io1.out.durable_open_v2, true); + CHECK_VAL(io1.out.timeout, 300*1000); + + lease_key2 = random(); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + smb2_lease_v2_create(&io21, &ls2, false /* dir */, fname, + lease_key2, NULL, smb2_util_lease_state("RWH"), lease_epoch2++); + } else { + smb2_oplock_create(&io21, fname, client2_level); + } + io21.in.durable_open = false; + io21.in.durable_open_v2 = true; + io21.in.persistent_open = false; + io21.in.create_guid = create_guid2; + io21.in.timeout = UINT32_MAX; + io24 = io23 = io22 = io21; + + req21 = smb2_create_send(tree2_1, &io21); + torture_assert(tctx, req21 != NULL, "req21"); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + const struct smb2_lease_break *lb = + &lease_break_info.lease_break; + const struct smb2_lease *l = &lb->current_lease; + const struct smb2_lease_key *k = &l->lease_key; + + torture_wait_for_lease_break(tctx); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 1); + + torture_assert(tctx, + lease_break_info.lease_transport == transport1, + "expect lease break on transport1\n"); + CHECK_VAL(k->data[0], lease_key1); + CHECK_VAL(k->data[1], ~lease_key1); + CHECK_VAL(lb->new_lease_state, + smb2_util_lease_state("RH")); + CHECK_VAL(lb->break_flags, + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); + CHECK_VAL(lb->new_epoch, lease_epoch1+1); + lease_epoch1 += 1; + } else { + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(lease_break_info.count, 0); + + torture_assert(tctx, + break_info.received_transport == transport1, + "expect oplock break on transport1\n"); + CHECK_VAL(break_info.handle.data[0], _h1.data[0]); + CHECK_VAL(break_info.handle.data[1], _h1.data[1]); + CHECK_VAL(break_info.level, SMB2_OPLOCK_LEVEL_II); + } + + torture_reset_break_info(tctx, &break_info); + break_info.oplock_skip_ack = true; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + WAIT_FOR_ASYNC_RESPONSE(tctx, req21); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + block_setup = test_setup_blocked_transports(tctx); + torture_assert(tctx, block_setup, "test_setup_blocked_transports"); + + blocked2_1 = _test_block_smb2_transport(tctx, transport2_1, "transport2_1"); + torture_assert_goto(tctx, blocked2_1, ret, done, "we could not block tcp transport"); + smb2cli_session_reset_channel_sequence(session2_1->smbXcli, csn2++); + + smb2cli_session_start_replay(session2_2->smbXcli); + transport2_2->options.request_timeout = 5; + status = smb2_create(tree2_2, tctx, &io22); + transport2_2->options.request_timeout = request_timeout2; + CHECK_STATUS(status, reject_status); + smb2cli_session_stop_replay(session2_2->smbXcli); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + blocked2_2 = _test_block_smb2_transport(tctx, transport2_2, "transport2_2"); + torture_assert_goto(tctx, blocked2_2, ret, done, "we could not block tcp transport"); + smb2cli_session_reset_channel_sequence(session2_2->smbXcli, csn2++); + + smb2cli_session_start_replay(session2_3->smbXcli); + transport2_3->options.request_timeout = 5; + status = smb2_create(tree2_3, tctx, &io23); + transport2_3->options.request_timeout = request_timeout2; + CHECK_STATUS(status, reject_status); + smb2cli_session_stop_replay(session2_3->smbXcli); + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + smb2_util_close(tree1, _h1); + h1 = NULL; + + status = smb2_create_recv(req21, tctx, &io21); + CHECK_STATUS(status, NT_STATUS_OK); + _h21 = io21.out.file.handle; + h21 = &_h21; + CHECK_CREATED(&io21, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io21.out.oplock_level, client2_level); + CHECK_VAL(io21.out.durable_open, false); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io21.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io21.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io21.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io21.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io21.out.durable_open_v2, true); + CHECK_VAL(io21.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io21.out.durable_open_v2, true); + CHECK_VAL(io21.out.timeout, 300*1000); + } else { + CHECK_VAL(io21.out.durable_open_v2, false); + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + + blocked2_3 = _test_block_smb2_transport(tctx, transport2_3, "transport2_3"); + torture_assert_goto(tctx, blocked2_3, ret, done, "we could not block tcp transport"); + smb2cli_session_reset_channel_sequence(session2_3->smbXcli, csn2++); + + smb2cli_session_start_replay(session2_4->smbXcli); + status = smb2_create(tree2_4, tctx, &io24); + smb2cli_session_stop_replay(session2_4->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + _h24 = io24.out.file.handle; + h24 = &_h24; + CHECK_CREATED(&io24, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(h24->data[0], h21->data[0]); + CHECK_VAL(h24->data[1], h21->data[1]); + if (client2_level == SMB2_OPLOCK_LEVEL_LEASE) { + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[0], lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_key.data[1], ~lease_key2); + CHECK_VAL(io24.out.lease_response_v2.lease_epoch, lease_epoch2); + CHECK_VAL(io24.out.lease_response_v2.lease_state, + smb2_util_lease_state("RHW")); + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else if (client2_level == SMB2_OPLOCK_LEVEL_BATCH) { + CHECK_VAL(io24.out.durable_open_v2, true); + CHECK_VAL(io24.out.timeout, 300*1000); + } else { + CHECK_VAL(io24.out.durable_open_v2, false); + } + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + status = smb2_util_close(tree2_4, *h24); + CHECK_STATUS(status, NT_STATUS_OK); + h24 = NULL; + + if (client1_level == SMB2_OPLOCK_LEVEL_LEASE) { + torture_wait_for_lease_break(tctx); + } else { + torture_wait_for_oplock_break(tctx); + } + CHECK_VAL(break_info.count, 0); + CHECK_VAL(lease_break_info.count, 0); + +done: + + if (blocked2_3) { + _test_unblock_smb2_transport(tctx, transport2_3, "transport2_3"); + } + if (blocked2_2) { + _test_unblock_smb2_transport(tctx, transport2_2, "transport2_2"); + } + if (blocked2_1) { + _test_unblock_smb2_transport(tctx, transport2_1, "transport2_1"); + } + if (block_setup) { + test_cleanup_blocked_transports(tctx); + } + + smbXcli_conn_disconnect(transport2_1->conn, NT_STATUS_LOCAL_DISCONNECT); + smbXcli_conn_disconnect(transport2_2->conn, NT_STATUS_LOCAL_DISCONNECT); + smbXcli_conn_disconnect(transport2_3->conn, NT_STATUS_LOCAL_DISCONNECT); + smbXcli_conn_disconnect(transport2_4->conn, NT_STATUS_LOCAL_DISCONNECT); + + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + + smb2_deltree(tree1, BASEDIR); + + TALLOC_FREE(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending3n_vs_lease_windows(). + */ +static bool test_dhv2_pending3n_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending3n_vs_lease_sane. + */ +static bool test_dhv2_pending3n_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending3n_vs_oplock_windows(). + */ +static bool test_dhv2_pending3n_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * but without asking for an oplock nor a lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending3n_vs_oplock_sane. + */ +static bool test_dhv2_pending3n_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_NONE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending3l_vs_oplock_windows(). + */ +static bool test_dhv2_pending3l_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending3l_vs_oplock_sane. + */ +static bool test_dhv2_pending3l_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending3l_vs_lease_windows(). + */ +static bool test_dhv2_pending3l_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a v2 lease. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending3l_vs_lease_sane(). + */ +static bool test_dhv2_pending3l_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_LEASE, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending3o_vs_oplock_windows(). + */ +static bool test_dhv2_pending3o_vs_oplock_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds a batch oplock. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending3o_vs_oplock_sane(). + */ +static bool test_dhv2_pending3o_vs_oplock_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_BATCH, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the sane reject status of + * NT_STATUS_FILE_NOT_AVAILABLE. + * + * It won't pass against Windows as it returns + * NT_STATUS_ACCESS_DENIED see + * test_dhv2_pending3o_vs_lease_windows(). + */ +static bool test_dhv2_pending3o_vs_lease_sane(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_FILE_NOT_AVAILABLE, + tree1, tree2_1); +} + +/** + * This tests replay with a pending open with 4 channels + * and blocked transports on the client side. + * + * With a durablev2 request containing a create_guid, + * a share_access of READ/WRITE/DELETE, + * and asking for a batch oplock. + * + * While another client holds an RWH lease. + * And allows share_access of READ/WRITE/DELETE. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14449 + * + * This expects the strange reject status of + * NT_STATUS_ACCESS_DENIED, which is returned + * by Windows Servers. + * + * It won't pass against Samba as it returns + * NT_STATUS_FILE_NOT_AVAILABLE. see + * test_dhv2_pending3o_vs_lease_sane(). + */ +static bool test_dhv2_pending3o_vs_lease_windows(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2_1) +{ + return _test_dhv2_pending3_vs_hold(tctx, __func__, + SMB2_OPLOCK_LEVEL_LEASE, + SMB2_OPLOCK_LEVEL_BATCH, + NT_STATUS_ACCESS_DENIED, + tree1, tree2_1); +} + +static bool test_channel_sequence_table(struct torture_context *tctx, + struct smb2_tree *tree, + bool do_replay, + uint16_t opcode) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle handle; + struct smb2_handle *phandle = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\channel_sequence.dat"; + uint16_t csn = 0; + uint16_t limit = UINT16_MAX - 0x7fff; + int i; + struct { + uint16_t csn; + bool csn_rand_low; + bool csn_rand_high; + NTSTATUS expected_status; + } tests[] = { + { + .csn = 0, + .expected_status = NT_STATUS_OK, + },{ + .csn = 0x7fff + 1, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = 0x7fff + 2, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = -1, + .csn_rand_high = true, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = 0xffff, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = 0x7fff, + .expected_status = NT_STATUS_OK, + },{ + .csn = 0x7ffe, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = 0, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = -1, + .csn_rand_low = true, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = 0x7fff + 1, + .expected_status = NT_STATUS_OK, + },{ + .csn = 0xffff, + .expected_status = NT_STATUS_OK, + },{ + .csn = 0, + .expected_status = NT_STATUS_OK, + },{ + .csn = 1, + .expected_status = NT_STATUS_OK, + },{ + .csn = 0, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + },{ + .csn = 1, + .expected_status = NT_STATUS_OK, + },{ + .csn = 0xffff, + .expected_status = NT_STATUS_FILE_NOT_AVAILABLE, + } + }; + + smb2cli_session_reset_channel_sequence(tree->session->smbXcli, 0); + + csn = smb2cli_session_current_channel_sequence(tree->session->smbXcli); + torture_comment(tctx, "Testing create with channel sequence number: 0x%04x\n", csn); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + torture_assert_ntstatus_ok_goto(tctx, + smb2_create(tree, mem_ctx, &io), + ret, done, "failed to call smb2_create"); + + handle = io.out.file.handle; + phandle = &handle; + + for (i=0; i <ARRAY_SIZE(tests); i++) { + + const char *opstr = ""; + union smb_fileinfo qfinfo; + + csn = tests[i].csn; + + if (tests[i].csn_rand_low) { + csn = rand() % limit; + } else if (tests[i].csn_rand_high) { + csn = rand() % limit + 0x7fff; + } + + switch (opcode) { + case SMB2_OP_WRITE: + opstr = "write"; + break; + case SMB2_OP_IOCTL: + opstr = "ioctl"; + break; + case SMB2_OP_SETINFO: + opstr = "setinfo"; + break; + default: + break; + } + + smb2cli_session_reset_channel_sequence(tree->session->smbXcli, csn); + csn = smb2cli_session_current_channel_sequence(tree->session->smbXcli); + + torture_comment(tctx, "Testing %s (replay: %s) with CSN 0x%04x, expecting: %s\n", + opstr, do_replay ? "true" : "false", csn, + nt_errstr(tests[i].expected_status)); + + if (do_replay) { + smb2cli_session_start_replay(tree->session->smbXcli); + } + + switch (opcode) { + case SMB2_OP_WRITE: { + DATA_BLOB blob = data_blob_talloc(tctx, NULL, 255); + + generate_random_buffer(blob.data, blob.length); + + status = smb2_util_write(tree, handle, blob.data, 0, blob.length); + if (NT_STATUS_IS_OK(status)) { + struct smb2_read rd; + + rd = (struct smb2_read) { + .in.file.handle = handle, + .in.length = blob.length, + .in.offset = 0 + }; + + torture_assert_ntstatus_ok_goto(tctx, + smb2_read(tree, tree, &rd), + ret, done, "failed to read after write"); + + torture_assert_data_blob_equal(tctx, + rd.out.data, blob, + "read/write mismatch"); + } + break; + } + case SMB2_OP_IOCTL: { + union smb_ioctl ioctl; + ioctl = (union smb_ioctl) { + .smb2.level = RAW_IOCTL_SMB2, + .smb2.in.file.handle = handle, + .smb2.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID, + .smb2.in.max_output_response = 64, + .smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL + }; + status = smb2_ioctl(tree, mem_ctx, &ioctl.smb2); + break; + } + case SMB2_OP_SETINFO: { + union smb_setfileinfo sfinfo; + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION; + sfinfo.generic.in.file.handle = handle; + sfinfo.position_information.in.position = 0x1000; + status = smb2_setinfo_file(tree, &sfinfo); + break; + } + default: + break; + } + + qfinfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_POSITION_INFORMATION, + .generic.in.file.handle = handle + }; + + torture_assert_ntstatus_ok_goto(tctx, + smb2_getinfo_file(tree, mem_ctx, &qfinfo), + ret, done, "failed to read after write"); + + if (do_replay) { + smb2cli_session_stop_replay(tree->session->smbXcli); + } + + torture_assert_ntstatus_equal_goto(tctx, + status, tests[i].expected_status, + ret, done, "got unexpected failure code"); + + } +done: + if (phandle != NULL) { + smb2_util_close(tree, *phandle); + } + + smb2_util_unlink(tree, fname); + + return ret; +} + +static bool test_channel_sequence(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + bool ret = true; + const char *fname = BASEDIR "\\channel_sequence.dat"; + struct smb2_transport *transport1 = tree->session->transport; + struct smb2_handle handle; + uint16_t opcodes[] = { SMB2_OP_WRITE, SMB2_OP_IOCTL, SMB2_OP_SETINFO }; + int i; + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "Replay tests\n"); + } + + torture_comment(tctx, "Testing channel sequence numbers\n"); + + smbXcli_conn_set_force_channel_sequence(transport1->conn, true); + + torture_assert_ntstatus_ok_goto(tctx, + torture_smb2_testdir(tree, BASEDIR, &handle), + ret, done, "failed to setup test directory"); + + smb2_util_close(tree, handle); + smb2_util_unlink(tree, fname); + + for (i=0; i <ARRAY_SIZE(opcodes); i++) { + torture_assert(tctx, + test_channel_sequence_table(tctx, tree, false, opcodes[i]), + "failed to test CSN without replay flag"); + torture_assert(tctx, + test_channel_sequence_table(tctx, tree, true, opcodes[i]), + "failed to test CSN with replay flag"); + } + +done: + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test Durability V2 Create Replay Detection on Multi Channel + */ +static bool test_replay3(struct torture_context *tctx, struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay3.dat"; + struct smb2_tree *tree2 = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2 = NULL; + struct smb2_session *session1_1 = tree1->session; + struct smb2_session *session1_2 = NULL; + uint32_t share_capabilities; + bool share_is_so; + uint32_t server_capabilities; + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "Replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, + "Server does not support multi-channel."); + } + + share_capabilities = smb2cli_tcon_capabilities(tree1->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + + torture_comment(tctx, "Replay of DurableHandleReqV2 on Multi " + "Channel\n"); + status = torture_smb2_testdir(tree1, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h); + smb2_util_unlink(tree1, fname); + CHECK_VAL(break_info.count, 0); + + /* + * use the 1st channel, 1st session + */ + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + tree1->session = session1_1; + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + if (share_is_so) { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.timeout, 0); + } else { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.timeout, 300*1000); + } + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(break_info.count, 0); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree2, + tctx->ev, + &transport1->options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2 = tree2->session->transport; + + transport2->oplock.handler = torture_oplock_ack_handler; + transport2->oplock.private_data = tree2; + + /* + * Now bind the 1st session to 2nd transport channel + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2, session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session1_2, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * use the 2nd channel, 1st session + */ + tree1->session = session1_2; + smb2cli_session_start_replay(tree1->session->smbXcli); + status = smb2_create(tree1, mem_ctx, &io); + smb2cli_session_stop_replay(tree1->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + if (share_is_so) { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.timeout, 0); + } else { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.timeout, 300*1000); + } + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(break_info.count, 0); + + tree1->session = session1_1; + smb2_util_close(tree1, *h); + h = NULL; + +done: + talloc_free(tree2); + tree1->session = session1_1; + + if (h != NULL) { + smb2_util_close(tree1, *h); + } + + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, BASEDIR); + + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test Multichannel IO Ordering using ChannelSequence/Channel Epoch number + */ +static bool test_replay4(struct torture_context *tctx, struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + uint8_t buf[64]; + struct smb2_read rd; + union smb_setfileinfo sfinfo; + bool ret = true; + const char *fname = BASEDIR "\\replay4.dat"; + struct smb2_tree *tree2 = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2 = NULL; + struct smb2_session *session1_1 = tree1->session; + struct smb2_session *session1_2 = NULL; + uint16_t curr_cs; + uint32_t share_capabilities; + bool share_is_so; + uint32_t server_capabilities; + + if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "Replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities( + tree1->session->transport->conn); + if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, + "Server does not support multi-channel."); + } + + share_capabilities = smb2cli_tcon_capabilities(tree1->smbXcli); + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + + torture_reset_break_info(tctx, &break_info); + transport1->oplock.handler = torture_oplock_ack_handler; + transport1->oplock.private_data = tree1; + + torture_comment(tctx, "IO Ordering for Multi Channel\n"); + status = torture_smb2_testdir(tree1, BASEDIR, &_h1); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree1, _h1); + smb2_util_unlink(tree1, fname); + CHECK_VAL(break_info.count, 0); + + /* + * use the 1st channel, 1st session + */ + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + tree1->session = session1_1; + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + if (share_is_so) { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s")); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.timeout, 0); + } else { + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.timeout, 300*1000); + } + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(break_info.count, 0); + + status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Increment ChannelSequence so that server thinks that there's a + * Channel Failure + */ + smb2cli_session_increment_channel_sequence(tree1->session->smbXcli); + + /* + * Perform a Read with incremented ChannelSequence + */ + rd = (struct smb2_read) { + .in.file.handle = *h1, + .in.length = sizeof(buf), + .in.offset = 0 + }; + status = smb2_read(tree1, tree1, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Performing a Write with Stale ChannelSequence is not allowed by + * server + */ + curr_cs = smb2cli_session_reset_channel_sequence( + tree1->session->smbXcli, 0); + status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_FILE_NOT_AVAILABLE); + + /* + * Performing a Write Replay with Stale ChannelSequence is not allowed + * by server + */ + smb2cli_session_start_replay(tree1->session->smbXcli); + smb2cli_session_reset_channel_sequence(tree1->session->smbXcli, 0); + status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf)); + smb2cli_session_stop_replay(tree1->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_FILE_NOT_AVAILABLE); + + /* + * Performing a SetInfo with stale ChannelSequence is not allowed by + * server + */ + ZERO_STRUCT(sfinfo); + sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION; + sfinfo.generic.in.file.handle = *h1; + sfinfo.position_information.in.position = 0x1000; + status = smb2_setinfo_file(tree1, &sfinfo); + CHECK_STATUS(status, NT_STATUS_FILE_NOT_AVAILABLE); + + /* + * Performing a Read with stale ChannelSequence is allowed + */ + rd = (struct smb2_read) { + .in.file.handle = *h1, + .in.length = ARRAY_SIZE(buf), + .in.offset = 0 + }; + status = smb2_read(tree1, tree1, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree2, + tctx->ev, + &transport1->options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport2 = tree2->session->transport; + + transport2->oplock.handler = torture_oplock_ack_handler; + transport2->oplock.private_data = tree2; + + /* + * Now bind the 1st session to 2nd transport channel + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2, session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session1_2, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * use the 2nd channel, 1st session + */ + tree1->session = session1_2; + + /* + * Write Replay with Correct ChannelSequence is allowed by the server + */ + smb2cli_session_start_replay(tree1->session->smbXcli); + smb2cli_session_reset_channel_sequence(tree1->session->smbXcli, + curr_cs); + status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2cli_session_stop_replay(tree1->session->smbXcli); + + /* + * See what happens if we change the Buffer and perform a Write Replay. + * This is to show that Write Replay does not really care about the data + */ + memset(buf, 'r', ARRAY_SIZE(buf)); + smb2cli_session_start_replay(tree1->session->smbXcli); + status = smb2_util_write(tree1, *h1, buf, 0, ARRAY_SIZE(buf)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2cli_session_stop_replay(tree1->session->smbXcli); + + /* + * Read back from File to verify what was written + */ + rd = (struct smb2_read) { + .in.file.handle = *h1, + .in.length = ARRAY_SIZE(buf), + .in.offset = 0 + }; + status = smb2_read(tree1, tree1, &rd); + CHECK_STATUS(status, NT_STATUS_OK); + + if ((rd.out.data.length != ARRAY_SIZE(buf)) || + memcmp(rd.out.data.data, buf, ARRAY_SIZE(buf))) { + torture_comment(tctx, "Write Replay Data Mismatch\n"); + } + + tree1->session = session1_1; + smb2_util_close(tree1, *h1); + h1 = NULL; + + if (share_is_so) { + CHECK_VAL(break_info.count, 1); + } else { + CHECK_VAL(break_info.count, 0); + } +done: + talloc_free(tree2); + tree1->session = session1_1; + + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, BASEDIR); + + talloc_free(tree1); + talloc_free(mem_ctx); + + return ret; +} + +/** + * Test Durability V2 Persistent Create Replay on a Single Channel + */ +static bool test_replay5(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io; + struct GUID create_guid = GUID_random(); + bool ret = true; + uint32_t share_capabilities; + bool share_is_ca; + bool share_is_so; + uint32_t server_capabilities; + const char *fname = BASEDIR "\\replay5.dat"; + struct smb2_transport *transport = tree->session->transport; + struct smbcli_options options = tree->session->transport->options; + uint8_t expect_oplock = smb2_util_oplock_level("b"); + NTSTATUS expect_status = NT_STATUS_DUPLICATE_OBJECTID; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "Replay tests\n"); + } + + server_capabilities = smb2cli_conn_server_capabilities( + tree->session->transport->conn); + if (!(server_capabilities & SMB2_CAP_PERSISTENT_HANDLES)) { + torture_skip(tctx, + "Server does not support persistent handles."); + } + + share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli); + + share_is_ca = share_capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY; + if (!share_is_ca) { + torture_skip(tctx, "Share is not continuously available."); + } + + share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT; + if (share_is_so) { + expect_oplock = smb2_util_oplock_level("s"); + expect_status = NT_STATUS_FILE_NOT_AVAILABLE; + } + + torture_reset_break_info(tctx, &break_info); + transport->oplock.handler = torture_oplock_ack_handler; + transport->oplock.private_data = tree; + + torture_comment(tctx, "Replay of Persistent DurableHandleReqV2 on Single " + "Channel\n"); + status = torture_smb2_testdir(tree, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h); + smb2_util_unlink(tree, fname); + CHECK_VAL(break_info.count, 0); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = true; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, expect_oplock); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, true); + CHECK_VAL(io.out.timeout, 300*1000); + CHECK_VAL(break_info.count, 0); + + /* disconnect, leaving the durable open */ + TALLOC_FREE(tree); + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + /* a re-open of a persistent handle causes an error */ + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, expect_status); + + /* SMB2_FLAGS_REPLAY_OPERATION must be set to open the Persistent Handle */ + smb2cli_session_start_replay(tree->session->smbXcli); + smb2cli_session_increment_channel_sequence(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.persistent_open, true); + CHECK_VAL(io.out.oplock_level, expect_oplock); + _h = io.out.file.handle; + h = &_h; + + smb2_util_close(tree, *h); + h = NULL; +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + + +/** + * Test Error Codes when a DurableHandleReqV2 with matching CreateGuid is + * re-sent with or without SMB2_FLAGS_REPLAY_OPERATION + */ +static bool test_replay6(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_handle _h; + struct smb2_handle *h = NULL; + struct smb2_create io, ref1; + union smb_fileinfo qfinfo; + struct GUID create_guid = GUID_random(); + bool ret = true; + const char *fname = BASEDIR "\\replay6.dat"; + struct smb2_transport *transport = tree->session->transport; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + torture_reset_break_info(tctx, &break_info); + tree->session->transport->oplock.handler = torture_oplock_ack_handler; + tree->session->transport->oplock.private_data = tree; + + torture_comment(tctx, "Error Codes for DurableHandleReqV2 Replay\n"); + smb2_util_unlink(tree, fname); + status = torture_smb2_testdir(tree, BASEDIR, &_h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, _h); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + torture_reset_break_info(tctx, &break_info); + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access("RWD"), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + ref1 = io; + _h = io.out.file.handle; + h = &_h; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_CREATE_OUT(&io, &ref1); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + torture_reset_break_info(tctx, &break_info); + + qfinfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_POSITION_INFORMATION, + .generic.in.file.handle = *h + }; + torture_comment(tctx, "Trying getinfo\n"); + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + CHECK_VAL(qfinfo.position_information.out.position, 0); + + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + torture_assert_u64_not_equal_goto(tctx, + io.out.file.handle.data[0], + ref1.out.file.handle.data[0], + ret, done, "data 0"); + torture_assert_u64_not_equal_goto(tctx, + io.out.file.handle.data[1], + ref1.out.file.handle.data[1], + ret, done, "data 1"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 1); + CHECK_VAL(break_info.level, smb2_util_oplock_level("s")); + torture_reset_break_info(tctx, &break_info); + + /* + * Resend the matching Durable V2 Create without + * SMB2_FLAGS_REPLAY_OPERATION. This triggers an oplock break and still + * gets NT_STATUS_DUPLICATE_OBJECTID + */ + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_DUPLICATE_OBJECTID); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + torture_reset_break_info(tctx, &break_info); + + /* + * According to MS-SMB2 3.3.5.9.10 if Durable V2 Create is replayed and + * FileAttributes or CreateDisposition do not match the earlier Create + * request the Server fails request with + * NT_STATUS_INVALID_PARAMETER. But through this test we see that server + * does not really care about changed FileAttributes or + * CreateDisposition. + */ + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + smb2cli_session_start_replay(tree->session->smbXcli); + status = smb2_create(tree, mem_ctx, &io); + smb2cli_session_stop_replay(tree->session->smbXcli); + CHECK_STATUS(status, NT_STATUS_OK); + torture_assert_u64_not_equal_goto(tctx, + io.out.file.handle.data[0], + ref1.out.file.handle.data[0], + ret, done, "data 0"); + torture_assert_u64_not_equal_goto(tctx, + io.out.file.handle.data[1], + ref1.out.file.handle.data[1], + ret, done, "data 1"); + torture_wait_for_oplock_break(tctx); + CHECK_VAL(break_info.count, 0); + +done: + if (h != NULL) { + smb2_util_close(tree, *h); + } + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, BASEDIR); + + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_replay7(struct torture_context *tctx, struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_transport *transport = tree->session->transport; + NTSTATUS status; + struct smb2_handle _dh; + struct smb2_handle *dh = NULL; + struct smb2_notify notify; + struct smb2_request *req; + union smb_fileinfo qfinfo; + bool ret = false; + + if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, "SMB 3.X Dialect family required for " + "replay tests\n"); + } + + torture_comment(tctx, "Notify across increment/decrement of csn\n"); + + smbXcli_conn_set_force_channel_sequence(transport->conn, true); + + status = torture_smb2_testdir(tree, BASEDIR, &_dh); + CHECK_STATUS(status, NT_STATUS_OK); + dh = &_dh; + + notify.in.recursive = 0x0000; + notify.in.buffer_size = 0xffff; + notify.in.file.handle = _dh; + notify.in.completion_filter = FILE_NOTIFY_CHANGE_FILE_NAME; + notify.in.unknown = 0x00000000; + + /* + * This posts a long-running request with csn==0 to "dh". Now + * op->request_count==1 in smb2_server.c. + */ + smb2cli_session_reset_channel_sequence(tree->session->smbXcli, 0); + req = smb2_notify_send(tree, ¬ify); + + qfinfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_POSITION_INFORMATION, + .generic.in.file.handle = _dh + }; + + /* + * This sequence of 2 dummy requests moves + * op->request_count==1 to op->pre_request_count. The numbers + * used avoid int16 overflow. + */ + + smb2cli_session_reset_channel_sequence(tree->session->smbXcli, 30000); + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2cli_session_reset_channel_sequence(tree->session->smbXcli, 60000); + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * This final request turns the op->global->channel_sequence + * to the same as we had when sending the notify above. The + * notify's request count has in the meantime moved to + * op->pre_request_count. + */ + + smb2cli_session_reset_channel_sequence(tree->session->smbXcli, 0); + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * At this point op->request_count==0. + * + * The next cancel makes us reply to the notify. Because the + * csn we currently use is the same as we used when sending + * the notify, smbd thinks it must decrement op->request_count + * and not op->pre_request_count. + */ + + status = smb2_cancel(req); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req, mem_ctx, ¬ify); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + ret = true; + +done: + if (dh != NULL) { + smb2_util_close(tree, _dh); + } + smb2_deltree(tree, BASEDIR); + talloc_free(tree); + talloc_free(mem_ctx); + + return ret; +} + +struct torture_suite *torture_smb2_replay_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "replay"); + + torture_suite_add_1smb2_test(suite, "replay-commands", test_replay_commands); + torture_suite_add_1smb2_test(suite, "replay-regular", test_replay_regular); + torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock1", test_replay_dhv2_oplock1); + torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock2", test_replay_dhv2_oplock2); + torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock3", test_replay_dhv2_oplock3); + torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock-lease", test_replay_dhv2_oplock_lease); + torture_suite_add_1smb2_test(suite, "replay-dhv2-lease1", test_replay_dhv2_lease1); + torture_suite_add_1smb2_test(suite, "replay-dhv2-lease2", test_replay_dhv2_lease2); + torture_suite_add_1smb2_test(suite, "replay-dhv2-lease3", test_replay_dhv2_lease3); + torture_suite_add_1smb2_test(suite, "replay-dhv2-lease-oplock", test_replay_dhv2_lease_oplock); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-violation-lease-close-sane", test_dhv2_pending1n_vs_violation_lease_close_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-violation-lease-ack-sane", test_dhv2_pending1n_vs_violation_lease_ack_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-violation-lease-close-windows", test_dhv2_pending1n_vs_violation_lease_close_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-violation-lease-ack-windows", test_dhv2_pending1n_vs_violation_lease_ack_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-oplock-sane", test_dhv2_pending1n_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-oplock-windows", test_dhv2_pending1n_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-lease-sane", test_dhv2_pending1n_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1n-vs-lease-windows", test_dhv2_pending1n_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending1l-vs-oplock-sane", test_dhv2_pending1l_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1l-vs-oplock-windows", test_dhv2_pending1l_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending1l-vs-lease-sane", test_dhv2_pending1l_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1l-vs-lease-windows", test_dhv2_pending1l_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending1o-vs-oplock-sane", test_dhv2_pending1o_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1o-vs-oplock-windows", test_dhv2_pending1o_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending1o-vs-lease-sane", test_dhv2_pending1o_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending1o-vs-lease-windows", test_dhv2_pending1o_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending2n-vs-oplock-sane", test_dhv2_pending2n_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending2n-vs-oplock-windows", test_dhv2_pending2n_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending2n-vs-lease-sane", test_dhv2_pending2n_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending2n-vs-lease-windows", test_dhv2_pending2n_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending2l-vs-oplock-sane", test_dhv2_pending2l_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending2l-vs-oplock-windows", test_dhv2_pending2l_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending2l-vs-lease-sane", test_dhv2_pending2l_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending2l-vs-lease-windows", test_dhv2_pending2l_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending2o-vs-oplock-sane", test_dhv2_pending2o_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending2o-vs-oplock-windows", test_dhv2_pending2o_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending2o-vs-lease-sane", test_dhv2_pending2o_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending2o-vs-lease-windows", test_dhv2_pending2o_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending3n-vs-oplock-sane", test_dhv2_pending3n_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending3n-vs-oplock-windows", test_dhv2_pending3n_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending3n-vs-lease-sane", test_dhv2_pending3n_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending3n-vs-lease-windows", test_dhv2_pending3n_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending3l-vs-oplock-sane", test_dhv2_pending3l_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending3l-vs-oplock-windows", test_dhv2_pending3l_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending3l-vs-lease-sane", test_dhv2_pending3l_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending3l-vs-lease-windows", test_dhv2_pending3l_vs_lease_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending3o-vs-oplock-sane", test_dhv2_pending3o_vs_oplock_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending3o-vs-oplock-windows", test_dhv2_pending3o_vs_oplock_windows); + torture_suite_add_2smb2_test(suite, "dhv2-pending3o-vs-lease-sane", test_dhv2_pending3o_vs_lease_sane); + torture_suite_add_2smb2_test(suite, "dhv2-pending3o-vs-lease-windows", test_dhv2_pending3o_vs_lease_windows); + torture_suite_add_1smb2_test(suite, "channel-sequence", test_channel_sequence); + torture_suite_add_1smb2_test(suite, "replay3", test_replay3); + torture_suite_add_1smb2_test(suite, "replay4", test_replay4); + torture_suite_add_1smb2_test(suite, "replay5", test_replay5); + torture_suite_add_1smb2_test(suite, "replay6", test_replay6); + torture_suite_add_1smb2_test(suite, "replay7", test_replay7); + + suite->description = talloc_strdup(suite, "SMB2 REPLAY tests"); + + return suite; +} diff --git a/source4/torture/smb2/samba3misc.c b/source4/torture/smb2/samba3misc.c new file mode 100644 index 0000000..cf4d9bb --- /dev/null +++ b/source4/torture/smb2/samba3misc.c @@ -0,0 +1,189 @@ +/* + Unix SMB/CIFS implementation. + + Test some misc Samba3 code paths + + Copyright (C) Volker Lendecke 2006 + Copyright (C) Stefan Metzmacher 2019 + + 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/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "system/filesys.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/util.h" +#include "lib/events/events.h" +#include "param/param.h" + +#define CHECK_STATUS(status, correct) do { \ + const char *_cmt = "(" __location__ ")"; \ + torture_assert_ntstatus_equal_goto(tctx,status,correct, \ + ret,done,_cmt); \ + } while (0) + +#define BASEDIR "samba3misc.smb2" + +#define WAIT_FOR_ASYNC_RESPONSE(req) \ + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { \ + if (tevent_loop_once(tctx->ev) != 0) { \ + break; \ + } \ + } + +static void torture_smb2_tree_disconnect_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + struct smb2_tree *tree = + talloc_get_type_abort(private_data, + struct smb2_tree); + + smbXcli_conn_disconnect(tree->session->transport->conn, + NT_STATUS_CTX_CLIENT_QUERY_TIMEOUT); +} + +/* + * Check that Samba3 correctly deals with conflicting local posix byte range + * locks on an underlying file via "normal" SMB2 (without posix extentions). + * + * Note: This test depends on "posix locking = yes". + * Note: To run this test, use "--option=torture:localdir=<LOCALDIR>" + */ +static bool torture_samba3_localposixlock1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + bool ret = true; + int rc; + const char *fname = "posixtimedlock.dat"; + const char *fpath; + const char *localdir; + const char *localname; + struct smb2_handle h = {{0}}; + struct smb2_lock lck = {0}; + struct smb2_lock_element el[1] = {{0}}; + struct smb2_request *req = NULL; + int fd = -1; + struct flock posix_lock; + struct tevent_timer *te; + + status = torture_smb2_testdir(tree, BASEDIR, &h); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h); + + status = torture_smb2_testfile(tree, fname, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + fpath = talloc_asprintf(tctx, "%s\\%s", BASEDIR, fname); + torture_assert(tctx, fpath != NULL, "fpath\n"); + + status = torture_smb2_testfile(tree, fpath, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + localdir = torture_setting_string(tctx, "localdir", NULL); + torture_assert(tctx, localdir != NULL, + "--option=torture:localdir=<LOCALDIR> required\n"); + + localname = talloc_asprintf(tctx, "%s/%s/%s", + localdir, BASEDIR, fname); + torture_assert(tctx, localname != NULL, "localname\n"); + + /* + * Lock a byte range from posix + */ + + torture_comment(tctx, " local open(%s)\n", localname); + fd = open(localname, O_RDWR); + if (fd == -1) { + torture_warning(tctx, "open(%s) failed: %s\n", + localname, strerror(errno)); + torture_assert(tctx, fd != -1, "open localname\n"); + } + + posix_lock.l_type = F_WRLCK; + posix_lock.l_whence = SEEK_SET; + posix_lock.l_start = 0; + posix_lock.l_len = 1; + + torture_comment(tctx, " local fcntl\n"); + rc = fcntl(fd, F_SETLK, &posix_lock); + if (rc == -1) { + torture_warning(tctx, "fcntl failed: %s\n", strerror(errno)); + torture_assert_goto(tctx, rc != -1, ret, done, + "fcntl lock\n"); + } + + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + + torture_comment(tctx, " remote non-blocking lock\n"); + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); + + torture_comment(tctx, " remote async blocking lock\n"); + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + req = smb2_lock_send(tree, &lck); + torture_assert_goto(tctx, req != NULL, ret, done, "smb2_lock_send()\n"); + + te = tevent_add_timer(tctx->ev, + tctx, timeval_current_ofs(5, 0), + torture_smb2_tree_disconnect_timer, + tree); + torture_assert_goto(tctx, te != NULL, ret, done, "tevent_add_timer\n"); + + torture_comment(tctx, " remote wait for STATUS_PENDING\n"); + WAIT_FOR_ASYNC_RESPONSE(req); + + torture_comment(tctx, " local close file\n"); + close(fd); + fd = -1; + + torture_comment(tctx, " remote lock should now succeed\n"); + status = smb2_lock_recv(req, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + if (fd != -1) { + close(fd); + } + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +struct torture_suite *torture_smb2_samba3misc_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "samba3misc"); + + torture_suite_add_1smb2_test(suite, "localposixlock1", + torture_samba3_localposixlock1); + + suite->description = talloc_strdup(suite, "SMB2 Samba3 MISC"); + + return suite; +} diff --git a/source4/torture/smb2/scan.c b/source4/torture/smb2/scan.c new file mode 100644 index 0000000..086cc75 --- /dev/null +++ b/source4/torture/smb2/scan.c @@ -0,0 +1,265 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 opcode scanner + + Copyright (C) Andrew Tridgell 2005 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "lib/cmdline/cmdline.h" +#include "torture/torture.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" + +#include "torture/smb2/proto.h" + +/* + scan for valid SMB2 getinfo levels +*/ +static bool torture_smb2_getinfo_scan(struct torture_context *tctx) +{ + struct smb2_tree *tree; + NTSTATUS status; + struct smb2_getinfo io; + struct smb2_handle fhandle, dhandle; + int c, i; + + static const char *FNAME = "scan-getinfo.dat"; + static const char *FNAME2 = "scan-getinfo.dat:2ndstream"; + static const char *DNAME = "scan-getinfo.dir"; + static const char *DNAME2 = "scan-getinfo.dir:2ndstream"; + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + + status = torture_setup_complex_file(tctx, tree, FNAME); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to setup complex file '%s': %s\n", + FNAME, nt_errstr(status)); + return false; + } + torture_setup_complex_file(tctx, tree, FNAME2); + + status = torture_setup_complex_dir(tctx, tree, DNAME); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to setup complex dir '%s': %s\n", + DNAME, nt_errstr(status)); + smb2_util_unlink(tree, FNAME); + return false; + } + torture_setup_complex_file(tctx, tree, DNAME2); + + torture_smb2_testfile(tree, FNAME, &fhandle); + torture_smb2_testdir(tree, DNAME, &dhandle); + + + ZERO_STRUCT(io); + io.in.output_buffer_length = 0xFFFF; + + for (c=1;c<5;c++) { + for (i=0;i<0x100;i++) { + io.in.info_type = c; + io.in.info_class = i; + + io.in.file.handle = fhandle; + status = smb2_getinfo(tree, tctx, &io); + if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS)) { + torture_comment(tctx, "file level 0x%02x:%02x %u is %ld bytes - %s\n", + io.in.info_type, io.in.info_class, + (unsigned)io.in.info_class, + (long)io.out.blob.length, nt_errstr(status)); + dump_data(1, io.out.blob.data, io.out.blob.length); + } + + io.in.file.handle = dhandle; + status = smb2_getinfo(tree, tctx, &io); + if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS)) { + torture_comment(tctx, "dir level 0x%02x:%02x %u is %ld bytes - %s\n", + io.in.info_type, io.in.info_class, + (unsigned)io.in.info_class, + (long)io.out.blob.length, nt_errstr(status)); + dump_data(1, io.out.blob.data, io.out.blob.length); + } + } + } + + smb2_util_unlink(tree, FNAME); + smb2_util_rmdir(tree, DNAME); + return true; +} + +/* + scan for valid SMB2 setinfo levels +*/ +static bool torture_smb2_setinfo_scan(struct torture_context *tctx) +{ + static const char *FNAME = "scan-setinfo.dat"; + static const char *FNAME2 = "scan-setinfo.dat:2ndstream"; + + struct smb2_tree *tree; + NTSTATUS status; + struct smb2_setinfo io; + struct smb2_handle handle; + int c, i; + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + + status = torture_setup_complex_file(tctx, tree, FNAME); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to setup complex file '%s': %s\n", + FNAME, nt_errstr(status)); + return false; + } + torture_setup_complex_file(tctx, tree, FNAME2); + + torture_smb2_testfile(tree, FNAME, &handle); + + ZERO_STRUCT(io); + io.in.blob = data_blob_talloc_zero(tctx, 1024); + + for (c=1;c<5;c++) { + for (i=0;i<0x100;i++) { + io.in.level = (i<<8) | c; + io.in.file.handle = handle; + status = smb2_setinfo(tree, &io); + if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS)) { + torture_comment(tctx, "file level 0x%04x - %s\n", + io.in.level, nt_errstr(status)); + } + } + } + + smb2_util_unlink(tree, FNAME); + return true; +} + + +/* + scan for valid SMB2 scan levels +*/ +static bool torture_smb2_find_scan(struct torture_context *tctx) +{ + struct smb2_tree *tree; + NTSTATUS status; + struct smb2_find io; + struct smb2_handle handle; + int i; + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + + torture_assert_ntstatus_ok(tctx, + smb2_util_roothandle(tree, &handle), + "Failed to open roothandle"); + + ZERO_STRUCT(io); + io.in.file.handle = handle; + io.in.pattern = "*"; + io.in.continue_flags = SMB2_CONTINUE_FLAG_RESTART; + io.in.max_response_size = 0x10000; + + for (i=1;i<0x100;i++) { + io.in.level = i; + + io.in.file.handle = handle; + status = smb2_find(tree, tctx, &io); + if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS) && + !NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) && + !NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + torture_comment(tctx, "find level 0x%04x is %ld bytes - %s\n", + io.in.level, (long)io.out.blob.length, nt_errstr(status)); + dump_data(1, io.out.blob.data, io.out.blob.length); + } + } + + return true; +} + +/* + scan for valid SMB2 opcodes +*/ +static bool torture_smb2_scan(struct torture_context *tctx) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_tree *tree; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + NTSTATUS status; + int opcode; + struct smb2_request *req; + struct smbcli_options options; + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + status = smb2_connect(mem_ctx, host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree, tctx->ev, &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, "Connection failed"); + + tree->session->transport->options.request_timeout = 3; + + for (opcode=0;opcode<1000;opcode++) { + req = smb2_request_init_tree(tree, opcode, 2, false, 0); + SSVAL(req->out.body, 0, 0); + smb2_transport_send(req); + if (!smb2_request_receive(req)) { + talloc_free(tree); + status = smb2_connect(mem_ctx, host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree, tctx->ev, &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(mem_ctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, "Connection failed"); + tree->session->transport->options.request_timeout = 3; + } else { + status = smb2_request_destroy(req); + torture_comment(tctx, "active opcode %4d gave status %s\n", opcode, nt_errstr(status)); + } + } + + talloc_free(mem_ctx); + + return true; +} + +struct torture_suite *torture_smb2_scan_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "scan"); + + torture_suite_add_simple_test(suite, "scan", torture_smb2_scan); + torture_suite_add_simple_test(suite, "getinfo", torture_smb2_getinfo_scan); + torture_suite_add_simple_test(suite, "setinfo", torture_smb2_setinfo_scan); + torture_suite_add_simple_test(suite, "find", torture_smb2_find_scan); + + suite->description = talloc_strdup(suite, "scan target (not a test)"); + + return suite; +} diff --git a/source4/torture/smb2/secleak.c b/source4/torture/smb2/secleak.c new file mode 100644 index 0000000..ca709ed --- /dev/null +++ b/source4/torture/smb2/secleak.c @@ -0,0 +1,91 @@ +/* + Unix SMB/CIFS implementation. + + find security related memory leaks + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) David Mulder 2020 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/libcli.h" +#include "torture/util.h" +#include "system/time.h" +#include "libcli/smb_composite/smb_composite.h" +#include "auth/credentials/credentials.h" +#include "param/param.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" + +static bool try_failed_login(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + struct cli_credentials *credentials = NULL; + uint32_t sessid = 0; + struct smb2_session *session = NULL; + bool result = true; + + session = smb2_session_init(tree->session->transport, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx); + torture_assert(tctx, session, "Session initialization failed"); + + sessid = smb2cli_session_current_id(tree->session->smbXcli); + credentials = cli_credentials_init(session); + torture_assert_goto(tctx, credentials, result, done, + "Credential allocation failed"); + + cli_credentials_set_conf(credentials, tctx->lp_ctx); + cli_credentials_set_domain(credentials, "INVALID-DOMAIN", CRED_SPECIFIED); + cli_credentials_set_username(credentials, "INVALID-USERNAME", CRED_SPECIFIED); + cli_credentials_set_password(credentials, "INVALID-PASSWORD", CRED_SPECIFIED); + + status = smb2_session_setup_spnego(session, credentials, sessid); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_LOGON_FAILURE, result, done, + "Allowed session setup with invalid credentials?!\n"); + +done: + /* smb2_session_init() steals the transport, and if we don't steal it + * back before freeing session, then we segfault on the next iteration + * because the transport pointer in the tree is now invalid. + */ + tree->session->transport = talloc_steal(tree->session, session->transport); + talloc_free(session); + + return result; +} + +bool torture_smb2_sec_leak(struct torture_context *tctx, struct smb2_tree *tree) +{ + time_t t1 = time_mono(NULL); + int timelimit = torture_setting_int(tctx, "timelimit", 20); + bool result; + + while (time_mono(NULL) < t1+timelimit) { + result = try_failed_login(tctx, tree); + torture_assert(tctx, result, + "Invalid credentials should have failed"); + + talloc_report(NULL, stdout); + } + + return true; +} diff --git a/source4/torture/smb2/sessid.c b/source4/torture/smb2/sessid.c new file mode 100644 index 0000000..bdcec05 --- /dev/null +++ b/source4/torture/smb2/sessid.c @@ -0,0 +1,100 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester + Copyright (C) Andrew Tridgell 1997-2003 + Copyright (C) Jelmer Vernooij 2006 + Copyright (C) David Mulder 2020 + + 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/>. +*/ + +#include "includes.h" +#include "torture/smbtorture.h" +#include "libcli/libcli.h" +#include "libcli/raw/raw_proto.h" +#include "system/filesys.h" +#include "system/time.h" +#include "libcli/resolve/resolve.h" +#include "lib/events/events.h" +#include "param/param.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/smb2/proto.h" +#include "libcli/smb/smbXcli_base.h" + + +static void smb2cli_session_set_id(struct smbXcli_session *session, + uint64_t session_id) +{ + smb2cli_session_set_id_and_flags(session, session_id, + smb2cli_session_get_flags(session)); +} + +/** + Try with a wrong session id and check error message. + */ + +bool run_sessidtest(struct torture_context *tctx, struct smb2_tree *tree) +{ + const char *fname = "sessid.tst"; + struct smb2_handle fnum; + struct smb2_create io = {0}; + uint32_t session_id; + union smb_fileinfo finfo; + + NTSTATUS status; + + smb2_util_unlink(tree, fname); + + io.in.fname = fname; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree, tree, &io); + if (NT_STATUS_IS_ERR(status)) { + torture_result(tctx, TORTURE_FAIL, "open of %s failed (%s)\n", + fname, nt_errstr(status)); + return false; + } + fnum = io.out.file.handle; + + session_id = smb2cli_session_current_id(tree->session->smbXcli); + smb2cli_session_set_id(tree->session->smbXcli, session_id+1234); + + torture_comment(tctx, "Testing qfileinfo with wrong sessid\n"); + + finfo.all_info2.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + finfo.all_info2.in.file.handle = fnum; + status = smb2_getinfo_file(tree, tctx, &finfo); + if (NT_STATUS_IS_OK(status)) { + torture_fail(tctx, "smb2_getinfo_file passed with wrong sessid"); + } + + torture_assert_ntstatus_equal(tctx, status, + NT_STATUS_USER_SESSION_DELETED, + "smb2_getinfo_file should have returned " + "NT_STATUS_USER_SESSION_DELETED"); + + smb2cli_session_set_id(tree->session->smbXcli, session_id); + + status = smb2_util_close(tree, fnum); + torture_assert_ntstatus_ok(tctx, status, + talloc_asprintf(tctx, "close failed (%s)", nt_errstr(status))); + + smb2_util_unlink(tree, fname); + + return true; +} diff --git a/source4/torture/smb2/session.c b/source4/torture/smb2/session.c new file mode 100644 index 0000000..e417008 --- /dev/null +++ b/source4/torture/smb2/session.c @@ -0,0 +1,5564 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 session setups + + Copyright (C) Michael Adam 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" +#include "../libcli/smb/smbXcli_base.h" +#include "lib/cmdline/cmdline.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "libcli/security/security.h" +#include "libcli/resolve/resolve.h" +#include "lib/param/param.h" +#include "lib/util/tevent_ntstatus.h" + +#define CHECK_CREATED(tctx, __io, __created, __attribute) \ + do { \ + torture_assert_int_equal(tctx, (__io)->out.create_action, \ + NTCREATEX_ACTION_ ## __created, \ + "out.create_action incorrect"); \ + torture_assert_int_equal(tctx, (__io)->out.size, 0, \ + "out.size incorrect"); \ + torture_assert_int_equal(tctx, (__io)->out.file_attr, \ + (__attribute), \ + "out.file_attr incorrect"); \ + torture_assert_int_equal(tctx, (__io)->out.reserved2, 0, \ + "out.reserverd2 incorrect"); \ + } while(0) + +#define WAIT_FOR_ASYNC_RESPONSE(req) \ + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { \ + if (tevent_loop_once(tctx->ev) != 0) { \ + break; \ + } \ + } + +/** + * basic test for doing a session reconnect + */ +bool test_session_reconnect1(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io1, io2; + uint64_t previous_session_id; + bool ret = true; + struct smb2_tree *tree2 = NULL; + union smb_fileinfo qfinfo; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_reconnect_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* disconnect, reconnect and then do durable reopen */ + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + torture_assert_goto(tctx, torture_smb2_connection_ext(tctx, previous_session_id, + &tree->session->transport->options, &tree2), + ret, done, + "session reconnect failed\n"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_USER_SESSION_DELETED, + ret, done, "smb2_getinfo_file " + "returned unexpected status"); + h1 = NULL; + + smb2_oplock_create_share(&io2, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree2, mem_ctx, &io2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + + CHECK_CREATED(tctx, &io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + _h2 = io2.out.file.handle; + h2 = &_h2; + +done: + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + if (h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree2 != NULL) { + smb2_util_unlink(tree2, fname); + } + smb2_util_unlink(tree, fname); + + talloc_free(tree); + talloc_free(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * basic test for doing a session reconnect on one connection + */ +bool test_session_reconnect2(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + uint64_t previous_session_id; + bool ret = true; + struct smb2_session *session2 = NULL; + union smb_fileinfo qfinfo; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_reconnect_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* disconnect, reconnect and then do durable reopen */ + previous_session_id = smb2cli_session_current_id(tree->session->smbXcli); + + torture_assert(tctx, torture_smb2_session_setup(tctx, tree->session->transport, + previous_session_id, tctx, &session2), + "session reconnect (on the same connection) failed"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_USER_SESSION_DELETED, + ret, done, "smb2_getinfo_file " + "returned unexpected status"); + h1 = NULL; + +done: + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + talloc_free(tree); + talloc_free(session2); + + talloc_free(mem_ctx); + + return ret; +} + +bool test_session_reauth1(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + bool ret = true; + union smb_fileinfo qfinfo; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_reauth1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + +done: + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +bool test_session_reauth2(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + bool ret = true; + union smb_fileinfo qfinfo; + struct cli_credentials *anon_creds = NULL; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_reauth2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + torture_assert(tctx, (anon_creds != NULL), "talloc error"); + + status = smb2_session_setup_spnego(tree->session, + anon_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* re-authenticate as original user again */ + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + +done: + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * test getting security descriptor after reauth + */ +bool test_session_reauth3(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + bool ret = true; + union smb_fileinfo qfinfo; + struct cli_credentials *anon_creds = NULL; + uint32_t secinfo_flags = SECINFO_OWNER + | SECINFO_GROUP + | SECINFO_DACL + | SECINFO_PROTECTED_DACL + | SECINFO_UNPROTECTED_DACL; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_reauth3_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + torture_assert(tctx, (anon_creds != NULL), "talloc error"); + + status = smb2_session_setup_spnego(tree->session, + anon_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* re-authenticate as original user again */ + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + +done: + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * test setting security descriptor after reauth. + */ +bool test_session_reauth4(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + bool ret = true; + union smb_fileinfo qfinfo; + union smb_setfileinfo sfinfo; + struct cli_credentials *anon_creds = NULL; + uint32_t secinfo_flags = SECINFO_OWNER + | SECINFO_GROUP + | SECINFO_DACL + | SECINFO_PROTECTED_DACL + | SECINFO_UNPROTECTED_DACL; + struct security_descriptor *sd1; + struct security_ace ace; + struct dom_sid *extra_sid; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_reauth4_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + sd1 = qfinfo.query_secdesc.out.sd; + + /* re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + torture_assert(tctx, (anon_creds != NULL), "talloc error"); + + status = smb2_session_setup_spnego(tree->session, + anon_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* give full access on the file to anonymous */ + + extra_sid = dom_sid_parse_talloc(tctx, SID_NT_ANONYMOUS); + + ZERO_STRUCT(ace); + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.access_mask = SEC_STD_ALL | SEC_FILE_ALL; + ace.trustee = *extra_sid; + + status = security_descriptor_dacl_add(sd1, &ace); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "security_descriptor_dacl_add failed"); + + ZERO_STRUCT(sfinfo); + sfinfo.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + sfinfo.set_secdesc.in.file.handle = _h1; + sfinfo.set_secdesc.in.secinfo_flags = SECINFO_DACL; + sfinfo.set_secdesc.in.sd = sd1; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed"); + + /* re-authenticate as original user again */ + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* re-get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + ret = true; + +done: + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * test renaming after reauth. + * compare security descriptors before and after rename/reauth + */ +bool test_session_reauth5(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char dname[128]; + char fname[256]; + char fname2[256]; + struct smb2_handle _dh1; + struct smb2_handle *dh1 = NULL; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + bool ret = true; + bool ok; + union smb_fileinfo qfinfo; + union smb_setfileinfo sfinfo; + struct cli_credentials *anon_creds = NULL; + uint32_t secinfo_flags = SECINFO_OWNER + | SECINFO_GROUP + | SECINFO_DACL + | SECINFO_PROTECTED_DACL + | SECINFO_UNPROTECTED_DACL; + struct security_descriptor *f_sd1; + struct security_descriptor *d_sd1 = NULL; + struct security_ace ace; + struct dom_sid *extra_sid; + + /* Add some random component to the file name. */ + snprintf(dname, sizeof(dname), "session_reauth5_%s.d", + generate_random_str(tctx, 8)); + snprintf(fname, sizeof(fname), "%s\\file.dat", dname); + + ok = smb2_util_setup_dir(tctx, tree, dname); + torture_assert(tctx, ok, "smb2_util_setup_dir not ok"); + + status = torture_smb2_testdir(tree, dname, &_dh1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed"); + dh1 = &_dh1; + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + f_sd1 = qfinfo.query_secdesc.out.sd; + + /* re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + torture_assert(tctx, (anon_creds != NULL), "talloc error"); + + status = smb2_session_setup_spnego(tree->session, + anon_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to rename the file: fails */ + + snprintf(fname2, sizeof(fname2), "%s\\file2.dat", dname); + + status = smb2_util_unlink(tree, fname2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_unlink failed"); + + + ZERO_STRUCT(sfinfo); + sfinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfinfo.rename_information.in.file.handle = _h1; + sfinfo.rename_information.in.overwrite = true; + sfinfo.rename_information.in.new_name = fname2; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_ACCESS_DENIED, + ret, done, "smb2_setinfo_file " + "returned unexpected status"); + + /* re-authenticate as original user again */ + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* give full access on the file to anonymous */ + + extra_sid = dom_sid_parse_talloc(tctx, SID_NT_ANONYMOUS); + + ZERO_STRUCT(ace); + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.access_mask = SEC_RIGHTS_FILE_ALL; + ace.trustee = *extra_sid; + + status = security_descriptor_dacl_add(f_sd1, &ace); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "security_descriptor_dacl_add failed"); + + ZERO_STRUCT(sfinfo); + sfinfo.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + sfinfo.set_secdesc.in.file.handle = _h1; + sfinfo.set_secdesc.in.secinfo_flags = secinfo_flags; + sfinfo.set_secdesc.in.sd = f_sd1; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed"); + + /* re-get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* re-authenticate as anonymous - again */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + torture_assert(tctx, (anon_creds != NULL), "talloc error"); + + status = smb2_session_setup_spnego(tree->session, + anon_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* try to rename the file: fails */ + + ZERO_STRUCT(sfinfo); + sfinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfinfo.rename_information.in.file.handle = _h1; + sfinfo.rename_information.in.overwrite = true; + sfinfo.rename_information.in.new_name = fname2; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_ACCESS_DENIED, + ret, done, "smb2_setinfo_file " + "returned unexpected status"); + + /* give full access on the parent dir to anonymous */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _dh1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + d_sd1 = qfinfo.query_secdesc.out.sd; + + ZERO_STRUCT(ace); + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.access_mask = SEC_RIGHTS_FILE_ALL; + ace.trustee = *extra_sid; + + status = security_descriptor_dacl_add(d_sd1, &ace); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "security_descriptor_dacl_add failed"); + + ZERO_STRUCT(sfinfo); + sfinfo.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + sfinfo.set_secdesc.in.file.handle = _dh1; + sfinfo.set_secdesc.in.secinfo_flags = secinfo_flags; + sfinfo.set_secdesc.in.secinfo_flags = SECINFO_DACL; + sfinfo.set_secdesc.in.sd = d_sd1; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed"); + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _dh1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + status = smb2_util_close(tree, _dh1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed"); + dh1 = NULL; + + /* try to rename the file: still fails */ + + ZERO_STRUCT(sfinfo); + sfinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfinfo.rename_information.in.file.handle = _h1; + sfinfo.rename_information.in.overwrite = true; + sfinfo.rename_information.in.new_name = fname2; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_ACCESS_DENIED, + ret, done, "smb2_setinfo_file " + "returned unexpected status"); + + /* re-authenticate as original user - again */ + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* rename the file - for verification that it works */ + + ZERO_STRUCT(sfinfo); + sfinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sfinfo.rename_information.in.file.handle = _h1; + sfinfo.rename_information.in.overwrite = true; + sfinfo.rename_information.in.new_name = fname2; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed"); + + /* closs the file, check it is gone and reopen under the new name */ + + status = smb2_util_close(tree, _h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed"); + ZERO_STRUCT(io1); + + smb2_generic_create_share(&io1, + NULL /* lease */, false /* dir */, + fname, + NTCREATEX_DISP_OPEN, + smb2_util_share_access(""), + smb2_util_oplock_level("b"), + 0 /* leasekey */, 0 /* leasestate */); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, "smb2_create " + "returned unexpected status"); + + ZERO_STRUCT(io1); + + smb2_generic_create_share(&io1, + NULL /* lease */, false /* dir */, + fname2, + NTCREATEX_DISP_OPEN, + smb2_util_share_access(""), + smb2_util_oplock_level("b"), + 0 /* leasekey */, 0 /* leasestate */); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* try to access the file via the old handle */ + + ZERO_STRUCT(qfinfo); + + qfinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + qfinfo.query_secdesc.in.file.handle = _h1; + qfinfo.query_secdesc.in.secinfo_flags = secinfo_flags; + + status = smb2_getinfo_file(tree, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + +done: + if (dh1 != NULL) { + smb2_util_close(tree, *dh1); + } + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + smb2_deltree(tree, dname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * do reauth with wrong credentials, + * hence triggering the error path in reauth. + * The invalid reauth deletes the session. + */ +bool test_session_reauth6(struct torture_context *tctx, struct smb2_tree *tree) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + bool ret = true; + char *corrupted_password; + struct cli_credentials *broken_creds; + bool ok; + bool encrypted; + NTSTATUS expected; + enum credentials_use_kerberos krb_state; + + krb_state = cli_credentials_get_kerberos_state( + samba_cmdline_get_creds()); + if (krb_state == CRED_USE_KERBEROS_REQUIRED) { + torture_skip(tctx, + "Can't test failing session setup with kerberos."); + } + + encrypted = smb2cli_tcon_is_encryption_on(tree->smbXcli); + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_reauth1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* + * reauthentication with invalid credentials: + */ + + broken_creds = cli_credentials_shallow_copy(mem_ctx, + samba_cmdline_get_creds()); + torture_assert(tctx, (broken_creds != NULL), "talloc error"); + + corrupted_password = talloc_asprintf(mem_ctx, "%s%s", + cli_credentials_get_password(broken_creds), + "corrupt"); + torture_assert(tctx, (corrupted_password != NULL), "talloc error"); + + ok = cli_credentials_set_password(broken_creds, corrupted_password, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_password not ok"); + + status = smb2_session_setup_spnego(tree->session, + broken_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_LOGON_FAILURE, ret, done, + "smb2_session_setup_spnego " + "returned unexpected status"); + + torture_comment(tctx, "did failed reauth\n"); + /* + * now verify that the invalid session reauth has closed our session + */ + + if (encrypted) { + expected = NT_STATUS_CONNECTION_DISCONNECTED; + } else { + expected = NT_STATUS_USER_SESSION_DELETED; + } + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree, mem_ctx, &io1); + torture_assert_ntstatus_equal_goto(tctx, status, expected, + ret, done, "smb2_create " + "returned unexpected status"); + +done: + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + smb2_util_unlink(tree, fname); + + talloc_free(tree); + + talloc_free(mem_ctx); + + return ret; +} + + +static bool test_session_expire1i(struct torture_context *tctx, + bool force_signing, + bool force_encryption) +{ + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + struct smb2_tree *tree = NULL; + enum credentials_use_kerberos use_kerberos; + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + union smb_fileinfo qfinfo; + size_t i; + + use_kerberos = cli_credentials_get_kerberos_state(credentials); + if (use_kerberos != CRED_USE_KERBEROS_REQUIRED) { + torture_warning(tctx, "smb2.session.expire1 requires -k yes!"); + torture_skip(tctx, "smb2.session.expire1 requires -k yes!"); + } + + torture_assert_int_equal(tctx, use_kerberos, CRED_USE_KERBEROS_REQUIRED, + "please use -k yes"); + + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + + lpcfg_set_option(tctx->lp_ctx, "gensec_gssapi:requested_life_time=4"); + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + if (force_signing) { + options.signing = SMB_SIGNING_REQUIRED; + } + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + + if (force_encryption) { + status = smb2cli_session_encryption_on(tree->session->smbXcli); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2cli_session_encryption_on failed"); + } + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_expire1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, tctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; + qfinfo.access_information.in.file.handle = _h1; + + for (i=0; i < 2; i++) { + torture_comment(tctx, "query info => OK\n"); + + ZERO_STRUCT(qfinfo.access_information.out); + status = smb2_getinfo_file(tree, tctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + torture_comment(tctx, "sleep 10 seconds\n"); + smb_msleep(10*1000); + + torture_comment(tctx, "query info => EXPIRED\n"); + ZERO_STRUCT(qfinfo.access_information.out); + status = smb2_getinfo_file(tree, tctx, &qfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_getinfo_file " + "returned unexpected status"); + + /* + * the krb5 library may not handle expired creds + * well, lets start with an empty ccache. + */ + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + + if (!force_encryption) { + smb2cli_session_require_signed_response( + tree->session->smbXcli, true); + } + + torture_comment(tctx, "reauth => OK\n"); + status = smb2_session_setup_spnego(tree->session, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + smb2cli_session_require_signed_response( + tree->session->smbXcli, false); + } + + ZERO_STRUCT(qfinfo.access_information.out); + status = smb2_getinfo_file(tree, tctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + ret = true; +done: + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + talloc_free(tree); + lpcfg_set_option(tctx->lp_ctx, "gensec_gssapi:requested_life_time=0"); + return ret; +} + +static bool test_session_expire1n(struct torture_context *tctx) +{ + return test_session_expire1i(tctx, + false, /* force_signing */ + false); /* force_encryption */ +} + +static bool test_session_expire1s(struct torture_context *tctx) +{ + return test_session_expire1i(tctx, + true, /* force_signing */ + false); /* force_encryption */ +} + +static bool test_session_expire1e(struct torture_context *tctx) +{ + return test_session_expire1i(tctx, + true, /* force_signing */ + true); /* force_encryption */ +} + +static bool test_session_expire2i(struct torture_context *tctx, + bool force_encryption) +{ + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + struct smb2_tree *tree = NULL; + const char *unc = NULL; + struct smb2_tree *tree2 = NULL; + struct tevent_req *subreq = NULL; + uint32_t timeout_msec; + enum credentials_use_kerberos use_kerberos; + uint32_t caps; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle dh2; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + union smb_fileinfo qfinfo; + union smb_setfileinfo sfinfo; + struct smb2_flush flsh; + struct smb2_read rd; + const uint8_t wd = 0; + struct smb2_lock lck; + struct smb2_lock_element el; + struct smb2_ioctl ctl; + struct smb2_break oack; + struct smb2_lease_break_ack lack; + struct smb2_find fnd; + union smb_search_data *d = NULL; + unsigned int count; + struct smb2_request *req = NULL; + struct smb2_notify ntf1; + struct smb2_notify ntf2; + + use_kerberos = cli_credentials_get_kerberos_state(credentials); + if (use_kerberos != CRED_USE_KERBEROS_REQUIRED) { + torture_warning(tctx, "smb2.session.expire2 requires -k yes!"); + torture_skip(tctx, "smb2.session.expire2 requires -k yes!"); + } + + torture_assert_int_equal(tctx, use_kerberos, CRED_USE_KERBEROS_REQUIRED, + "please use -k yes"); + + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + + lpcfg_set_option(tctx->lp_ctx, "gensec_gssapi:requested_life_time=4"); + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + options.signing = SMB_SIGNING_REQUIRED; + + unc = talloc_asprintf(tctx, "\\\\%s\\%s", host, share); + torture_assert(tctx, unc != NULL, "talloc_asprintf"); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + + if (force_encryption) { + status = smb2cli_session_encryption_on(tree->session->smbXcli); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2cli_session_encryption_on failed"); + } + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_expire2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + status = smb2_util_roothandle(tree, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_roothandle failed"); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, tctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; + qfinfo.access_information.in.file.handle = _h1; + + torture_comment(tctx, "query info => OK\n"); + + ZERO_STRUCT(qfinfo.access_information.out); + status = smb2_getinfo_file(tree, tctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + torture_comment(tctx, "lock => OK\n"); + ZERO_STRUCT(lck); + lck.in.locks = ⪙ + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = *h1; + ZERO_STRUCT(el); + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + el.offset = 0x0000000000000000; + el.length = 0x0000000000000001; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_lock lock failed"); + + torture_comment(tctx, "1st notify => PENDING\n"); + ZERO_STRUCT(ntf1); + ntf1.in.file.handle = dh; + ntf1.in.recursive = 0x0000; + ntf1.in.buffer_size = 128; + ntf1.in.completion_filter= FILE_NOTIFY_CHANGE_ATTRIBUTES; + ntf1.in.unknown = 0x00000000; + req = smb2_notify_send(tree, &ntf1); + + while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { + if (tevent_loop_once(tctx->ev) != 0) { + break; + } + } + + torture_assert_goto(tctx, req->state <= SMB2_REQUEST_RECV, ret, done, + "smb2_notify finished"); + + torture_comment(tctx, "sleep 10 seconds\n"); + smb_msleep(10*1000); + + torture_comment(tctx, "query info => EXPIRED\n"); + ZERO_STRUCT(qfinfo.access_information.out); + status = smb2_getinfo_file(tree, tctx, &qfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_getinfo_file " + "returned unexpected status"); + + + torture_comment(tctx, "set info => EXPIRED\n"); + ZERO_STRUCT(sfinfo); + sfinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sfinfo.end_of_file_info.in.file.handle = *h1; + sfinfo.end_of_file_info.in.size = 1; + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_setinfo_file " + "returned unexpected status"); + + torture_comment(tctx, "flush => EXPIRED\n"); + ZERO_STRUCT(flsh); + flsh.in.file.handle = *h1; + status = smb2_flush(tree, &flsh); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_flush " + "returned unexpected status"); + + torture_comment(tctx, "read => EXPIRED\n"); + ZERO_STRUCT(rd); + rd.in.file.handle = *h1; + rd.in.length = 5; + rd.in.offset = 0; + status = smb2_read(tree, tctx, &rd); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_read " + "returned unexpected status"); + + torture_comment(tctx, "write => EXPIRED\n"); + status = smb2_util_write(tree, *h1, &wd, 0, 1); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_util_write " + "returned unexpected status"); + + torture_comment(tctx, "ioctl => EXPIRED\n"); + ZERO_STRUCT(ctl); + ctl.in.file.handle = *h1; + ctl.in.function = FSCTL_SRV_ENUM_SNAPS; + ctl.in.max_output_response = 16; + ctl.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + status = smb2_ioctl(tree, tctx, &ctl); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_ioctl " + "returned unexpected status"); + + torture_comment(tctx, "oplock ack => EXPIRED\n"); + ZERO_STRUCT(oack); + oack.in.file.handle = *h1; + status = smb2_break(tree, &oack); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_break " + "returned unexpected status"); + + if (caps & SMB2_CAP_LEASING) { + torture_comment(tctx, "lease ack => EXPIRED\n"); + ZERO_STRUCT(lack); + lack.in.lease.lease_version = 1; + lack.in.lease.lease_key.data[0] = 1; + lack.in.lease.lease_key.data[1] = 2; + status = smb2_lease_break_ack(tree, &lack); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_break " + "returned unexpected status"); + } + + torture_comment(tctx, "query directory => EXPIRED\n"); + ZERO_STRUCT(fnd); + fnd.in.file.handle = dh; + fnd.in.pattern = "*"; + fnd.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE; + fnd.in.max_response_size= 0x100; + fnd.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + status = smb2_find_level(tree, tree, &fnd, &count, &d); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_find_level " + "returned unexpected status"); + + torture_comment(tctx, "1st notify => CANCEL\n"); + smb2_cancel(req); + + torture_comment(tctx, "2nd notify => EXPIRED\n"); + ZERO_STRUCT(ntf2); + ntf2.in.file.handle = dh; + ntf2.in.recursive = 0x0000; + ntf2.in.buffer_size = 128; + ntf2.in.completion_filter= FILE_NOTIFY_CHANGE_ATTRIBUTES; + ntf2.in.unknown = 0x00000000; + status = smb2_notify(tree, tctx, &ntf2); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_notify " + "returned unexpected status"); + + torture_assert_goto(tctx, req->state > SMB2_REQUEST_RECV, ret, done, + "smb2_notify (1st) not finished"); + + status = smb2_notify_recv(req, tctx, &ntf1); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_CANCELLED, + ret, done, "smb2_notify cancelled" + "returned unexpected status"); + + torture_comment(tctx, "tcon => EXPIRED\n"); + tree2 = smb2_tree_init(tree->session, tctx, false); + torture_assert(tctx, tree2 != NULL, "smb2_tree_init"); + timeout_msec = tree->session->transport->options.request_timeout * 1000; + subreq = smb2cli_tcon_send(tree2, tctx->ev, + tree2->session->transport->conn, + timeout_msec, + tree2->session->smbXcli, + tree2->smbXcli, + 0, /* flags */ + unc); + torture_assert(tctx, subreq != NULL, "smb2cli_tcon_send"); + torture_assert(tctx, + tevent_req_poll_ntstatus(subreq, tctx->ev, &status), + "tevent_req_poll_ntstatus"); + status = smb2cli_tcon_recv(subreq); + TALLOC_FREE(subreq); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2cli_tcon" + "returned unexpected status"); + + torture_comment(tctx, "create => EXPIRED\n"); + status = smb2_util_roothandle(tree, &dh2); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_util_roothandle" + "returned unexpected status"); + + torture_comment(tctx, "tdis => EXPIRED\n"); + status = smb2_tdis(tree); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2cli_tdis" + "returned unexpected status"); + + /* + * (Un)Lock, Close and Logoff are still possible + */ + + torture_comment(tctx, "1st unlock => OK\n"); + el.flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_lock unlock failed"); + + torture_comment(tctx, "2nd unlock => RANGE_NOT_LOCKED\n"); + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_RANGE_NOT_LOCKED, + ret, done, "smb2_lock 2nd unlock" + "returned unexpected status"); + + torture_comment(tctx, "lock => EXPIRED\n"); + el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_util_roothandle" + "returned unexpected status"); + + torture_comment(tctx, "close => OK\n"); + status = smb2_util_close(tree, *h1); + h1 = NULL; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close failed"); + + torture_comment(tctx, "echo without session => OK\n"); + status = smb2_keepalive(tree->session->transport); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_keepalive without session failed"); + + torture_comment(tctx, "echo with session => OK\n"); + req = smb2_keepalive_send(tree->session->transport, tree->session); + status = smb2_keepalive_recv(req); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_keepalive with session failed"); + + torture_comment(tctx, "logoff => OK\n"); + status = smb2_logoff(tree->session); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_logoff failed"); + + ret = true; +done: + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + talloc_free(tree); + lpcfg_set_option(tctx->lp_ctx, "gensec_gssapi:requested_life_time=0"); + return ret; +} + +static bool test_session_expire2s(struct torture_context *tctx) +{ + return test_session_expire2i(tctx, + false); /* force_encryption */ +} + +static bool test_session_expire2e(struct torture_context *tctx) +{ + return test_session_expire2i(tctx, + true); /* force_encryption */ +} + +static bool test_session_expire_disconnect(struct torture_context *tctx) +{ + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + struct smb2_tree *tree = NULL; + enum credentials_use_kerberos use_kerberos; + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + union smb_fileinfo qfinfo; + bool connected; + + use_kerberos = cli_credentials_get_kerberos_state(credentials); + if (use_kerberos != CRED_USE_KERBEROS_REQUIRED) { + torture_warning(tctx, "smb2.session.expire1 requires -k yes!"); + torture_skip(tctx, "smb2.session.expire1 requires -k yes!"); + } + + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + + lpcfg_set_option(tctx->lp_ctx, "gensec_gssapi:requested_life_time=4"); + lpcfg_smbcli_options(tctx->lp_ctx, &options); + options.signing = SMB_SIGNING_REQUIRED; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + + smbXcli_session_set_disconnect_expired(tree->session->smbXcli); + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_expire1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + + status = smb2_create(tree, tctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* get the security descriptor */ + + ZERO_STRUCT(qfinfo); + + qfinfo.access_information.level = RAW_FILEINFO_ACCESS_INFORMATION; + qfinfo.access_information.in.file.handle = _h1; + + torture_comment(tctx, "query info => OK\n"); + + ZERO_STRUCT(qfinfo.access_information.out); + status = smb2_getinfo_file(tree, tctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + torture_comment(tctx, "sleep 10 seconds\n"); + smb_msleep(10*1000); + + torture_comment(tctx, "query info => EXPIRED\n"); + ZERO_STRUCT(qfinfo.access_information.out); + status = smb2_getinfo_file(tree, tctx, &qfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, + NT_STATUS_NETWORK_SESSION_EXPIRED, + ret, done, "smb2_getinfo_file " + "returned unexpected status"); + + connected = smbXcli_conn_is_connected(tree->session->transport->conn); + torture_assert_goto(tctx, !connected, ret, done, "connected\n"); + + ret = true; +done: + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + + if (h1 != NULL) { + smb2_util_close(tree, *h1); + } + + talloc_free(tree); + lpcfg_set_option(tctx->lp_ctx, "gensec_gssapi:requested_life_time=0"); + return ret; +} + +bool test_session_bind1(struct torture_context *tctx, struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + union smb_fileinfo qfinfo; + bool ret = false; + struct smb2_tree *tree2 = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options options2; + struct smb2_transport *transport2 = NULL; + struct smb2_session *session1_1 = tree1->session; + struct smb2_session *session1_2 = NULL; + struct smb2_session *session2_1 = NULL; + struct smb2_session *session2_2 = NULL; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(transport1->conn); + if (!(caps & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, "server doesn't support SMB2_CAP_MULTI_CHANNEL\n"); + } + + /* + * We always want signing for this test! + */ + smb2cli_tcon_should_sign(tree1->smbXcli, true); + options2 = transport1->options; + options2.signing = SMB_SIGNING_REQUIRED; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "session_bind1_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree1, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2, + tctx->ev, + &options2, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + session2_2 = tree2->session; + transport2 = tree2->session->transport; + + /* + * Now bind the 2nd transport connection to the 1st session + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2, + session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session1_2, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* use the 1st connection, 1st session */ + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + tree1->session = session1_1; + status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* use the 2nd connection, 1st session */ + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + tree1->session = session1_2; + status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + tree1->session = session1_1; + status = smb2_util_close(tree1, *h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed"); + h1 = NULL; + + /* + * Now bind the 1st transport connection to the 2nd session + */ + session2_1 = smb2_session_channel(transport1, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree1, + session2_2); + torture_assert(tctx, session2_1 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_1, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + tree2->session = session2_1; + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_unlink failed"); + ret = true; +done: + talloc_free(tree2); + tree1->session = session1_1; + + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + + smb2_util_unlink(tree1, fname); + + talloc_free(tree1); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_session_bind2(struct torture_context *tctx, struct smb2_tree *tree1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *credentials = samba_cmdline_get_creds(); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname1[256]; + char fname2[256]; + struct smb2_handle _h1f1; + struct smb2_handle *h1f1 = NULL; + struct smb2_handle _h1f2; + struct smb2_handle *h1f2 = NULL; + struct smb2_handle _h2f2; + struct smb2_handle *h2f2 = NULL; + struct smb2_create io1f1; + struct smb2_create io1f2; + struct smb2_create io2f1; + struct smb2_create io2f2; + union smb_fileinfo qfinfo; + bool ret = false; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options options2; + struct smb2_tree *tree2 = NULL; + struct smb2_transport *transport2 = NULL; + struct smbcli_options options3; + struct smb2_tree *tree3 = NULL; + struct smb2_transport *transport3 = NULL; + struct smb2_session *session1_1 = tree1->session; + struct smb2_session *session1_2 = NULL; + struct smb2_session *session1_3 = NULL; + struct smb2_session *session2_1 = NULL; + struct smb2_session *session2_2 = NULL; + struct smb2_session *session2_3 = NULL; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(transport1->conn); + if (!(caps & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, "server doesn't support SMB2_CAP_MULTI_CHANNEL\n"); + } + + /* + * We always want signing for this test! + */ + smb2cli_tcon_should_sign(tree1->smbXcli, true); + options2 = transport1->options; + options2.signing = SMB_SIGNING_REQUIRED; + + /* Add some random component to the file name. */ + snprintf(fname1, sizeof(fname1), "session_bind2_1_%s.dat", + generate_random_str(tctx, 8)); + snprintf(fname2, sizeof(fname2), "session_bind2_2_%s.dat", + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + + smb2_oplock_create_share(&io1f1, fname1, + smb2_util_share_access(""), + smb2_util_oplock_level("")); + smb2_oplock_create_share(&io1f2, fname2, + smb2_util_share_access(""), + smb2_util_oplock_level("")); + + status = smb2_create(tree1, mem_ctx, &io1f1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1f1 = io1f1.out.file.handle; + h1f1 = &_h1f1; + CHECK_CREATED(tctx, &io1f1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1f1.out.oplock_level, + smb2_util_oplock_level(""), + "oplock_level incorrect"); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2, + tctx->ev, + &options2, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + session2_2 = tree2->session; + transport2 = tree2->session->transport; + smb2cli_tcon_should_sign(tree2->smbXcli, true); + + smb2_oplock_create_share(&io2f1, fname1, + smb2_util_share_access(""), + smb2_util_oplock_level("")); + smb2_oplock_create_share(&io2f2, fname2, + smb2_util_share_access(""), + smb2_util_oplock_level("")); + + status = smb2_create(tree2, mem_ctx, &io2f2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h2f2 = io2f2.out.file.handle; + h2f2 = &_h2f2; + CHECK_CREATED(tctx, &io2f2, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io2f2.out.oplock_level, + smb2_util_oplock_level(""), + "oplock_level incorrect"); + + options3 = transport1->options; + options3.signing = SMB_SIGNING_REQUIRED; + options3.only_negprot = true; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree3, + tctx->ev, + &options3, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + transport3 = tree3->session->transport; + + /* + * Create a fake session for the 2nd transport connection to the 1st session + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree1, + session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + + /* + * Now bind the 3rd transport connection to the 1st session + */ + session1_3 = smb2_session_channel(transport3, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree1, + session1_1); + torture_assert(tctx, session1_3 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session1_3, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* + * Create a fake session for the 1st transport connection to the 2nd session + */ + session2_1 = smb2_session_channel(transport1, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2, + session2_2); + torture_assert(tctx, session2_1 != NULL, "smb2_session_channel failed"); + + /* + * Now bind the 3rd transport connection to the 2nd session + */ + session2_3 = smb2_session_channel(transport3, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2, + session2_2); + torture_assert(tctx, session2_3 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_3, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1f1; + tree1->session = session1_1; + status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + tree1->session = session1_2; + status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_getinfo_file failed"); + tree1->session = session1_3; + status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h2f2; + tree2->session = session2_1; + status = smb2_getinfo_file(tree2, mem_ctx, &qfinfo); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_getinfo_file failed"); + tree2->session = session2_2; + status = smb2_getinfo_file(tree2, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + tree2->session = session2_3; + status = smb2_getinfo_file(tree2, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + tree1->session = session1_1; + status = smb2_create(tree1, mem_ctx, &io1f2); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done, + "smb2_create failed"); + tree1->session = session1_2; + status = smb2_create(tree1, mem_ctx, &io1f2); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_create failed"); + tree1->session = session1_3; + status = smb2_create(tree1, mem_ctx, &io1f2); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done, + "smb2_create failed"); + + tree2->session = session2_1; + status = smb2_create(tree2, mem_ctx, &io2f1); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_create failed"); + tree2->session = session2_2; + status = smb2_create(tree2, mem_ctx, &io2f1); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done, + "smb2_create failed"); + tree2->session = session2_3; + status = smb2_create(tree2, mem_ctx, &io2f1); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done, + "smb2_create failed"); + + smbXcli_conn_disconnect(transport3->conn, NT_STATUS_LOCAL_DISCONNECT); + smb_msleep(500); + + tree1->session = session1_1; + status = smb2_create(tree1, mem_ctx, &io1f2); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done, + "smb2_create failed"); + tree1->session = session1_2; + status = smb2_create(tree1, mem_ctx, &io1f2); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_create failed"); + + tree2->session = session2_1; + status = smb2_create(tree2, mem_ctx, &io2f1); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_create failed"); + tree2->session = session2_2; + status = smb2_create(tree2, mem_ctx, &io2f1); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done, + "smb2_create failed"); + + smbXcli_conn_disconnect(transport2->conn, NT_STATUS_LOCAL_DISCONNECT); + smb_msleep(500); + h2f2 = NULL; + + tree1->session = session1_1; + status = smb2_create(tree1, mem_ctx, &io1f2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1f2 = io1f2.out.file.handle; + h1f2 = &_h1f2; + CHECK_CREATED(tctx, &io1f2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1f2.out.oplock_level, + smb2_util_oplock_level(""), + "oplock_level incorrect"); + + tree1->session = session1_1; + status = smb2_util_close(tree1, *h1f1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed"); + h1f1 = NULL; + + ret = true; +done: + + smbXcli_conn_disconnect(transport3->conn, NT_STATUS_LOCAL_DISCONNECT); + smbXcli_conn_disconnect(transport2->conn, NT_STATUS_LOCAL_DISCONNECT); + + tree1->session = session1_1; + tree2->session = session2_2; + + if (h1f1 != NULL) { + smb2_util_close(tree1, *h1f1); + } + if (h1f2 != NULL) { + smb2_util_close(tree1, *h1f2); + } + if (h2f2 != NULL) { + smb2_util_close(tree2, *h2f2); + } + + smb2_util_unlink(tree1, fname1); + smb2_util_unlink(tree1, fname2); + + talloc_free(tree1); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_session_bind_auth_mismatch(struct torture_context *tctx, + struct smb2_tree *tree1, + const char *testname, + struct cli_credentials *creds1, + struct cli_credentials *creds2, + bool creds2_require_ok) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + union smb_fileinfo qfinfo; + bool ret = false; + struct smb2_tree *tree2 = NULL; + struct smb2_transport *transport1 = tree1->session->transport; + struct smbcli_options options2; + struct smb2_transport *transport2 = NULL; + struct smb2_session *session1_1 = tree1->session; + struct smb2_session *session1_2 = NULL; + struct smb2_session *session2_1 = NULL; + struct smb2_session *session2_2 = NULL; + struct smb2_session *session3_1 = NULL; + uint32_t caps; + bool encrypted; + bool creds2_got_ok = false; + + encrypted = smb2cli_tcon_is_encryption_on(tree1->smbXcli); + + caps = smb2cli_conn_server_capabilities(transport1->conn); + if (!(caps & SMB2_CAP_MULTI_CHANNEL)) { + torture_skip(tctx, "server doesn't support SMB2_CAP_MULTI_CHANNEL\n"); + } + + /* + * We always want signing for this test! + */ + smb2cli_tcon_should_sign(tree1->smbXcli, true); + options2 = transport1->options; + options2.signing = SMB_SIGNING_REQUIRED; + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "%s_%s.dat", testname, + generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + status = smb2_create(tree1, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + creds1, + &tree2, + tctx->ev, + &options2, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect failed"); + session2_2 = tree2->session; + transport2 = tree2->session->transport; + + /* + * Now bind the 2nd transport connection to the 1st session + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2, + session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session1_2, + creds1, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + + /* use the 1st connection, 1st session */ + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + tree1->session = session1_1; + status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* use the 2nd connection, 1st session */ + ZERO_STRUCT(qfinfo); + qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo.generic.in.file.handle = _h1; + tree1->session = session1_2; + status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + tree1->session = session1_1; + status = smb2_util_close(tree1, *h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed"); + h1 = NULL; + + /* + * Create a 3rd session in order to check if the invalid (creds2) + * are mapped to guest. + */ + session3_1 = smb2_session_init(transport1, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tctx); + torture_assert(tctx, session3_1 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session3_1, + creds2, + 0 /* previous_session_id */); + if (creds2_require_ok) { + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego worked"); + creds2_got_ok = true; + } else if (NT_STATUS_IS_OK(status)) { + bool authentiated = smbXcli_session_is_authenticated(session3_1->smbXcli); + torture_assert(tctx, !authentiated, "Invalid credentials allowed!"); + creds2_got_ok = true; + } else { + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_LOGON_FAILURE, ret, done, + "smb2_session_setup_spnego worked"); + } + + /* + * Now bind the 1st transport connection to the 2nd session + */ + session2_1 = smb2_session_channel(transport1, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree1, + session2_2); + torture_assert(tctx, session2_1 != NULL, "smb2_session_channel failed"); + + tree2->session = session2_1; + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_util_unlink worked on invalid channel"); + + status = smb2_session_setup_spnego(session2_1, + creds2, + 0 /* previous_session_id */); + if (creds2_got_ok) { + /* + * attaching with a different user (guest or anonymous) results + * in ACCESS_DENIED. + */ + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, ret, done, + "smb2_session_setup_spnego worked"); + } else { + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_LOGON_FAILURE, ret, done, + "smb2_session_setup_spnego worked"); + } + + tree2->session = session2_1; + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_util_unlink worked on invalid channel"); + + tree2->session = session2_2; + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_unlink failed"); + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, + "smb2_util_unlink worked"); + if (creds2_got_ok) { + /* + * We got ACCESS_DENIED on the session bind + * with a different user, now check that + * the correct user can actually bind on + * the same connection. + */ + TALLOC_FREE(session2_1); + session2_1 = smb2_session_channel(transport1, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree1, + session2_2); + torture_assert(tctx, session2_1 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session2_1, + creds1, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_session_setup_spnego failed"); + tree2->session = session2_1; + status = smb2_util_unlink(tree2, fname); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, + "smb2_util_unlink worked"); + tree2->session = session2_2; + } + + tree1->session = session1_1; + status = smb2_util_unlink(tree1, fname); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, + "smb2_util_unlink worked"); + + tree1->session = session1_2; + status = smb2_util_unlink(tree1, fname); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, + "smb2_util_unlink worked"); + + if (creds2_got_ok) { + /* + * With valid credentials, there's no point to test a failing + * reauth. + */ + ret = true; + goto done; + } + + /* + * Do a failing reauth the 2nd channel + */ + status = smb2_session_setup_spnego(session1_2, + creds2, + 0 /* previous_session_id */); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_LOGON_FAILURE, ret, done, + "smb2_session_setup_spnego worked"); + + tree1->session = session1_1; + status = smb2_util_unlink(tree1, fname); + if (encrypted) { + torture_assert_goto(tctx, !smbXcli_conn_is_connected(transport1->conn), ret, done, + "smb2_util_unlink worked"); + } else { + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_util_unlink worked"); + } + + tree1->session = session1_2; + status = smb2_util_unlink(tree1, fname); + if (encrypted) { + torture_assert_goto(tctx, !smbXcli_conn_is_connected(transport2->conn), ret, done, + "smb2_util_unlink worked"); + } else { + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_USER_SESSION_DELETED, ret, done, + "smb2_util_unlink worked"); + } + + status = smb2_util_unlink(tree2, fname); + if (encrypted) { + torture_assert_goto(tctx, !smbXcli_conn_is_connected(transport1->conn), ret, done, + "smb2_util_unlink worked"); + torture_assert_goto(tctx, !smbXcli_conn_is_connected(transport2->conn), ret, done, + "smb2_util_unlink worked"); + } else { + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, ret, done, + "smb2_util_unlink worked"); + } + + ret = true; +done: + talloc_free(tree2); + tree1->session = session1_1; + + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + + smb2_util_unlink(tree1, fname); + + talloc_free(tree1); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_session_bind_invalid_auth(struct torture_context *tctx, struct smb2_tree *tree1) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + struct cli_credentials *invalid_credentials = NULL; + bool ret = false; + + invalid_credentials = cli_credentials_init(tctx); + torture_assert(tctx, (invalid_credentials != NULL), "talloc error"); + cli_credentials_set_username(invalid_credentials, "__none__invalid__none__", CRED_SPECIFIED); + cli_credentials_set_domain(invalid_credentials, "__none__invalid__none__", CRED_SPECIFIED); + cli_credentials_set_password(invalid_credentials, "__none__invalid__none__", CRED_SPECIFIED); + cli_credentials_set_realm(invalid_credentials, NULL, CRED_SPECIFIED); + cli_credentials_set_workstation(invalid_credentials, "", CRED_UNINITIALISED); + + ret = test_session_bind_auth_mismatch(tctx, tree1, __func__, + credentials, + invalid_credentials, + false); + return ret; +} + +static bool test_session_bind_different_user(struct torture_context *tctx, struct smb2_tree *tree1) +{ + struct cli_credentials *credentials1 = samba_cmdline_get_creds(); + struct cli_credentials *credentials2 = torture_user2_credentials(tctx, tctx); + char *u1 = cli_credentials_get_unparsed_name(credentials1, tctx); + char *u2 = cli_credentials_get_unparsed_name(credentials2, tctx); + bool ret = false; + bool bval; + + torture_assert(tctx, (credentials2 != NULL), "talloc error"); + bval = cli_credentials_is_anonymous(credentials2); + if (bval) { + torture_skip(tctx, "valid user2 credentials are required"); + } + bval = strequal(u1, u2); + if (bval) { + torture_skip(tctx, "different user2 credentials are required"); + } + + ret = test_session_bind_auth_mismatch(tctx, tree1, __func__, + credentials1, + credentials2, + true); + return ret; +} + +static bool test_session_bind_negative_smbXtoX(struct torture_context *tctx, + const char *testname, + struct cli_credentials *credentials, + const struct smbcli_options *options1, + const struct smbcli_options *options2, + NTSTATUS bind_reject_status) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + NTSTATUS status; + bool ret = false; + struct smb2_tree *tree1 = NULL; + struct smb2_session *session1_1 = NULL; + char fname[256]; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + union smb_fileinfo qfinfo1; + struct smb2_tree *tree2_0 = NULL; + struct smb2_transport *transport2 = NULL; + struct smb2_session *session1_2 = NULL; + uint64_t session1_id = 0; + uint16_t session1_flags = 0; + NTSTATUS deleted_status = NT_STATUS_USER_SESSION_DELETED; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree1, + tctx->ev, + options1, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect options1 failed"); + session1_1 = tree1->session; + session1_id = smb2cli_session_current_id(session1_1->smbXcli); + session1_flags = smb2cli_session_get_flags(session1_1->smbXcli); + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "%s_%s.dat", + testname, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + status = smb2_create(tree1, tctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials, + &tree2_0, + tctx->ev, + options2, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect options2 failed"); + transport2 = tree2_0->session->transport; + + /* + * Now bind the 2nd transport connection to the 1st session + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2_0, + session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session1_2, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_equal_goto(tctx, status, bind_reject_status, ret, done, + "smb2_session_setup_spnego failed"); + if (NT_STATUS_IS_OK(bind_reject_status)) { + ZERO_STRUCT(qfinfo1); + qfinfo1.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo1.generic.in.file.handle = _h1; + tree1->session = session1_2; + status = smb2_getinfo_file(tree1, tctx, &qfinfo1); + tree1->session = session1_1; + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + } + TALLOC_FREE(session1_2); + + /* Check the initial session is still alive */ + ZERO_STRUCT(qfinfo1); + qfinfo1.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo1.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree1, tctx, &qfinfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + if (NT_STATUS_IS_OK(bind_reject_status)) { + deleted_status = NT_STATUS_ACCESS_DENIED; + bind_reject_status = NT_STATUS_ACCESS_DENIED; + } + + /* + * I guess this is not part of MultipleChannel_Negative_SMB2002, + * but we should also check the status without + * SMB2_SESSION_FLAG_BINDING. + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2_0, + session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + session1_2->needs_bind = false; + + status = smb2_session_setup_spnego(session1_2, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_equal_goto(tctx, status, deleted_status, ret, done, + "smb2_session_setup_spnego failed"); + TALLOC_FREE(session1_2); + + /* + * ... and we should also check the status without any existing + * session keys. + */ + session1_2 = smb2_session_init(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2_0); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + talloc_steal(tree2_0->session, transport2); + smb2cli_session_set_id_and_flags(session1_2->smbXcli, + session1_id, session1_flags); + + status = smb2_session_setup_spnego(session1_2, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_equal_goto(tctx, status, deleted_status, ret, done, + "smb2_session_setup_spnego failed"); + TALLOC_FREE(session1_2); + + /* Check the initial session is still alive */ + ZERO_STRUCT(qfinfo1); + qfinfo1.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo1.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree1, tctx, &qfinfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* + * Now bind the 2nd transport connection to the 1st session (again) + */ + session1_2 = smb2_session_channel(transport2, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + tree2_0, + session1_1); + torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed"); + + status = smb2_session_setup_spnego(session1_2, + credentials, + 0 /* previous_session_id */); + torture_assert_ntstatus_equal_goto(tctx, status, bind_reject_status, ret, done, + "smb2_session_setup_spnego failed"); + TALLOC_FREE(session1_2); + + /* Check the initial session is still alive */ + ZERO_STRUCT(qfinfo1); + qfinfo1.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo1.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree1, tctx, &qfinfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + ret = true; +done: + talloc_free(tree2_0); + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + talloc_free(tree1); + + return ret; +} + +/* + * This is similar to the MultipleChannel_Negative_SMB2002 test + * from the Windows Protocol Test Suite. + * + * It demonstrates that the server needs to do lookup + * in the global session table in order to get the signing + * and error code of invalid session setups correct. + * + * See: https://bugzilla.samba.org/show_bug.cgi?id=14512 + * + * Note you can ignore tree0... + */ +static bool test_session_bind_negative_smb202(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.02 if encryption is required"); + } + + options1 = transport0->options; + options1.client_guid = GUID_zero(); + options1.max_protocol = PROTOCOL_SMB2_02; + + options2 = options1; + options2.only_negprot = true; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_NOT_ACCEPTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb210s(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.max_protocol = PROTOCOL_SMB2_10; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_NOT_ACCEPTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb210d(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.max_protocol = PROTOCOL_SMB2_10; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_NOT_ACCEPTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb2to3s(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, + "Can't test without SMB3 support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB2_02; + options1.max_protocol = PROTOCOL_SMB2_10; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_00; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_INVALID_PARAMETER); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb2to3d(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, + "Can't test without SMB3 support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB2_02; + options1.max_protocol = PROTOCOL_SMB2_10; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_00; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_INVALID_PARAMETER); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3to2s(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, + "Can't test without SMB3 support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_00; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB2_02; + options2.max_protocol = PROTOCOL_SMB2_10; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_NOT_ACCEPTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3to2d(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_00) { + torture_skip(tctx, + "Can't test without SMB3 support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_00; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB2_02; + options2.max_protocol = PROTOCOL_SMB2_10; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_NOT_ACCEPTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3to3s(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_02; + options1.max_protocol = PROTOCOL_SMB3_02; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_11; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_INVALID_PARAMETER); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3to3d(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_02; + options1.max_protocol = PROTOCOL_SMB3_02; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_11; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_INVALID_PARAMETER); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3encGtoCs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_INVALID_PARAMETER); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3encGtoCd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_INVALID_PARAMETER); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signCtoHs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_OK); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signCtoHd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_OK); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signHtoCs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_OK); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signHtoCd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_OK); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signHtoGs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signHtoGd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signCtoGs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signCtoGd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoCs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoCd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoHs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoHd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneGtoCs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneGtoCd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneGtoHs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneGtoHd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneCtoGs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneCtoGd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneHtoGs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3sneHtoGd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + options2.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signC30toGs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_00; + options1.max_protocol = PROTOCOL_SMB3_02; + options1.signing = SMB_SIGNING_REQUIRED; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_11; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signC30toGd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_00; + options1.max_protocol = PROTOCOL_SMB3_02; + options1.signing = SMB_SIGNING_REQUIRED; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_11; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signH2XtoGs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_OFF, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB2_02; + options1.max_protocol = PROTOCOL_SMB2_10; + options1.signing = SMB_SIGNING_REQUIRED; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_11; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signH2XtoGd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_OFF, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB2_02; + options1.max_protocol = PROTOCOL_SMB2_10; + options1.signing = SMB_SIGNING_REQUIRED; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_11; + options2.max_protocol = PROTOCOL_SMB3_11; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_NOT_SUPPORTED); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoC30s(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_00; + options2.max_protocol = PROTOCOL_SMB3_02; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoC30d(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB3_00; + options2.max_protocol = PROTOCOL_SMB3_02; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoH2Xs(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* same client guid */ + options2 = options1; + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB2_02; + options2.max_protocol = PROTOCOL_SMB2_10; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_bind_negative_smb3signGtoH2Xd(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + struct smbcli_options options2; + bool ok; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test SMB 2.10 if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + /* different client guid */ + options2 = options1; + options2.client_guid = GUID_random(); + options2.only_negprot = true; + options2.min_protocol = PROTOCOL_SMB2_02; + options2.max_protocol = PROTOCOL_SMB2_10; + options2.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_bind_negative_smbXtoX(tctx, __func__, + credentials, + &options1, &options2, + NT_STATUS_REQUEST_OUT_OF_SEQUENCE); + talloc_free(tree0); + return ret; +} + +static bool test_session_two_logoff(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + NTSTATUS status; + bool ret = true; + struct smbcli_options transport2_options; + struct smb2_tree *tree2 = NULL; + struct smb2_session *session2 = NULL; + struct smb2_session *session1 = tree1->session; + struct smb2_transport *transport1 = tree1->session->transport; + struct smb2_transport *transport2; + bool ok; + + /* Connect 2nd connection */ + torture_comment(tctx, "connect tree2 with the same client_guid\n"); + transport2_options = transport1->options; + ok = torture_smb2_connection_ext(tctx, 0, &transport2_options, &tree2); + torture_assert(tctx, ok, "couldn't connect tree2\n"); + transport2 = tree2->session->transport; + session2 = tree2->session; + + torture_comment(tctx, "session2: logoff\n"); + status = smb2_logoff(session2); + torture_assert_ntstatus_ok(tctx, status, "session2: logoff"); + torture_comment(tctx, "transport2: keepalive\n"); + status = smb2_keepalive(transport2); + torture_assert_ntstatus_ok(tctx, status, "transport2: keepalive"); + torture_comment(tctx, "transport2: disconnect\n"); + TALLOC_FREE(tree2); + + torture_comment(tctx, "session1: logoff\n"); + status = smb2_logoff(session1); + torture_assert_ntstatus_ok(tctx, status, "session1: logoff"); + torture_comment(tctx, "transport1: keepalive\n"); + status = smb2_keepalive(transport1); + torture_assert_ntstatus_ok(tctx, status, "transport1: keepalive"); + torture_comment(tctx, "transport1: disconnect\n"); + TALLOC_FREE(tree1); + + return ret; +} + +static bool test_session_sign_enc(struct torture_context *tctx, + const char *testname, + struct cli_credentials *credentials1, + const struct smbcli_options *options1) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + NTSTATUS status; + bool ret = false; + struct smb2_tree *tree1 = NULL; + char fname[256]; + struct smb2_handle rh = {{0}}; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_create io1; + union smb_fileinfo qfinfo1; + union smb_notify notify; + struct smb2_request *req = NULL; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + credentials1, + &tree1, + tctx->ev, + options1, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_connect options1 failed"); + + status = smb2_util_roothandle(tree1, &rh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_roothandle failed"); + + /* Add some random component to the file name. */ + snprintf(fname, sizeof(fname), "%s_%s.dat", + testname, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + smb2_oplock_create_share(&io1, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + + io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + status = smb2_create(tree1, tctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed"); + _h1 = io1.out.file.handle; + h1 = &_h1; + CHECK_CREATED(tctx, &io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + torture_assert_int_equal(tctx, io1.out.oplock_level, + smb2_util_oplock_level("b"), + "oplock_level incorrect"); + + /* Check the initial session is still alive */ + ZERO_STRUCT(qfinfo1); + qfinfo1.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo1.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree1, tctx, &qfinfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + /* ask for a change notify, + on file or directory name changes */ + ZERO_STRUCT(notify); + notify.smb2.level = RAW_NOTIFY_SMB2; + notify.smb2.in.buffer_size = 1000; + notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + notify.smb2.in.file.handle = rh; + notify.smb2.in.recursive = true; + + req = smb2_notify_send(tree1, &(notify.smb2)); + WAIT_FOR_ASYNC_RESPONSE(req); + + status = smb2_cancel(req); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_cancel failed"); + + status = smb2_notify_recv(req, tctx, &(notify.smb2)); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_CANCELLED, + ret, done, + "smb2_notify_recv failed"); + + /* Check the initial session is still alive */ + ZERO_STRUCT(qfinfo1); + qfinfo1.generic.level = RAW_FILEINFO_POSITION_INFORMATION; + qfinfo1.generic.in.file.handle = _h1; + status = smb2_getinfo_file(tree1, tctx, &qfinfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed"); + + ret = true; +done: + if (h1 != NULL) { + smb2_util_close(tree1, *h1); + } + TALLOC_FREE(tree1); + + return ret; +} + +static bool test_session_signing_hmac_sha_256(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test signing only if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_HMAC_SHA256, + }, + }; + + ret = test_session_sign_enc(tctx, + __func__, + credentials, + &options1); + TALLOC_FREE(tree0); + return ret; +} + +static bool test_session_signing_aes_128_cmac(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test signing only if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_CMAC, + }, + }; + + ret = test_session_sign_enc(tctx, + __func__, + credentials, + &options1); + TALLOC_FREE(tree0); + return ret; +} + +static bool test_session_signing_aes_128_gmac(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials = samba_cmdline_get_creds(); + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + bool encrypted; + + encrypted = smb2cli_tcon_is_encryption_on(tree0->smbXcli); + if (encrypted) { + torture_skip(tctx, + "Can't test signing only if encrytion is required"); + } + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.signing = (struct smb3_signing_capabilities) { + .num_algos = 1, + .algos = { + SMB2_SIGNING_AES128_GMAC, + }, + }; + + ret = test_session_sign_enc(tctx, + __func__, + credentials, + &options1); + TALLOC_FREE(tree0); + return ret; +} + +static bool test_session_encryption_aes_128_ccm(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_CCM, + }, + }; + + ret = test_session_sign_enc(tctx, + __func__, + credentials, + &options1); + TALLOC_FREE(tree0); + return ret; +} + +static bool test_session_encryption_aes_128_gcm(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES128_GCM, + }, + }; + + ret = test_session_sign_enc(tctx, + __func__, + credentials, + &options1); + TALLOC_FREE(tree0); + return ret; +} + +static bool test_session_encryption_aes_256_ccm(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES256_CCM, + }, + }; + + ret = test_session_sign_enc(tctx, + __func__, + credentials, + &options1); + TALLOC_FREE(tree0); + return ret; +} + +static bool test_session_encryption_aes_256_gcm(struct torture_context *tctx, struct smb2_tree *tree0) +{ + struct cli_credentials *credentials0 = samba_cmdline_get_creds(); + struct cli_credentials *credentials = NULL; + bool ret = false; + struct smb2_transport *transport0 = tree0->session->transport; + struct smbcli_options options1; + bool ok; + + if (smbXcli_conn_protocol(transport0->conn) < PROTOCOL_SMB3_11) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 support"); + } + + if (smb2cli_conn_server_signing_algo(transport0->conn) < SMB2_SIGNING_AES128_GMAC) { + torture_skip(tctx, + "Can't test without SMB 3.1.1 signing negotiation support"); + } + + credentials = cli_credentials_shallow_copy(tctx, credentials0); + torture_assert(tctx, credentials != NULL, "cli_credentials_shallow_copy"); + ok = cli_credentials_set_smb_encryption(credentials, + SMB_ENCRYPTION_REQUIRED, + CRED_SPECIFIED); + torture_assert(tctx, ok, "cli_credentials_set_smb_encryption"); + + options1 = transport0->options; + options1.client_guid = GUID_random(); + options1.min_protocol = PROTOCOL_SMB3_11; + options1.max_protocol = PROTOCOL_SMB3_11; + options1.signing = SMB_SIGNING_REQUIRED; + options1.smb3_capabilities.encryption = (struct smb3_encryption_capabilities) { + .num_algos = 1, + .algos = { + SMB2_ENCRYPTION_AES256_GCM, + }, + }; + + ret = test_session_sign_enc(tctx, + __func__, + credentials, + &options1); + TALLOC_FREE(tree0); + return ret; +} + +static bool test_session_ntlmssp_bug14932(struct torture_context *tctx, struct smb2_tree *tree) +{ + struct cli_credentials *ntlm_creds = + cli_credentials_shallow_copy(tctx, samba_cmdline_get_creds()); + NTSTATUS status; + bool ret = true; + /* + * This is a NTLMv2_RESPONSE with the strange + * NTLMv2_CLIENT_CHALLENGE used by the net diag + * tool. + * + * As we expect an error anyway we fill the + * Response part with 0xab... + */ + static const char *netapp_magic = + "\xab\xab\xab\xab\xab\xab\xab\xab" + "\xab\xab\xab\xab\xab\xab\xab\xab" + "\x01\x01\x00\x00\x00\x00\x00\x00" + "\x3f\x3f\x3f\x3f\x3f\x3f\x3f\x3f" + "\xb8\x82\x3a\xf1\xb3\xdd\x08\x15" + "\x00\x00\x00\x00\x11\xa2\x08\x81" + "\x50\x38\x22\x78\x2b\x94\x47\xfe" + "\x54\x94\x7b\xff\x17\x27\x5a\xb4" + "\xf4\x18\xba\xdc\x2c\x38\xfd\x5b" + "\xfb\x0e\xc1\x85\x1e\xcc\x92\xbb" + "\x9b\xb1\xc4\xd5\x53\x14\xff\x8c" + "\x76\x49\xf5\x45\x90\x19\xa2"; + DATA_BLOB lm_response = data_blob_talloc_zero(tctx, 24); + DATA_BLOB lm_session_key = data_blob_talloc_zero(tctx, 16); + DATA_BLOB nt_response = data_blob_const(netapp_magic, 95); + DATA_BLOB nt_session_key = data_blob_talloc_zero(tctx, 16); + + cli_credentials_set_kerberos_state(ntlm_creds, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); + cli_credentials_set_ntlm_response(ntlm_creds, + &lm_response, + &lm_session_key, + &nt_response, + &nt_session_key, + CRED_SPECIFIED); + status = smb2_session_setup_spnego(tree->session, + ntlm_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_INVALID_PARAMETER, + "smb2_session_setup_spnego failed"); + + return ret; +} + +struct torture_suite *torture_smb2_session_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "session"); + + torture_suite_add_1smb2_test(suite, "reconnect1", test_session_reconnect1); + torture_suite_add_1smb2_test(suite, "reconnect2", test_session_reconnect2); + torture_suite_add_1smb2_test(suite, "reauth1", test_session_reauth1); + torture_suite_add_1smb2_test(suite, "reauth2", test_session_reauth2); + torture_suite_add_1smb2_test(suite, "reauth3", test_session_reauth3); + torture_suite_add_1smb2_test(suite, "reauth4", test_session_reauth4); + torture_suite_add_1smb2_test(suite, "reauth5", test_session_reauth5); + torture_suite_add_1smb2_test(suite, "reauth6", test_session_reauth6); + torture_suite_add_simple_test(suite, "expire1n", test_session_expire1n); + torture_suite_add_simple_test(suite, "expire1s", test_session_expire1s); + torture_suite_add_simple_test(suite, "expire1e", test_session_expire1e); + torture_suite_add_simple_test(suite, "expire2s", test_session_expire2s); + torture_suite_add_simple_test(suite, "expire2e", test_session_expire2e); + torture_suite_add_simple_test(suite, "expire_disconnect", + test_session_expire_disconnect); + torture_suite_add_1smb2_test(suite, "bind1", test_session_bind1); + torture_suite_add_1smb2_test(suite, "bind2", test_session_bind2); + torture_suite_add_1smb2_test(suite, "bind_invalid_auth", test_session_bind_invalid_auth); + torture_suite_add_1smb2_test(suite, "bind_different_user", test_session_bind_different_user); + torture_suite_add_1smb2_test(suite, "bind_negative_smb202", test_session_bind_negative_smb202); + torture_suite_add_1smb2_test(suite, "bind_negative_smb210s", test_session_bind_negative_smb210s); + torture_suite_add_1smb2_test(suite, "bind_negative_smb210d", test_session_bind_negative_smb210d); + torture_suite_add_1smb2_test(suite, "bind_negative_smb2to3s", test_session_bind_negative_smb2to3s); + torture_suite_add_1smb2_test(suite, "bind_negative_smb2to3d", test_session_bind_negative_smb2to3d); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3to2s", test_session_bind_negative_smb3to2s); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3to2d", test_session_bind_negative_smb3to2d); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3to3s", test_session_bind_negative_smb3to3s); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3to3d", test_session_bind_negative_smb3to3d); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3encGtoCs", test_session_bind_negative_smb3encGtoCs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3encGtoCd", test_session_bind_negative_smb3encGtoCd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signCtoHs", test_session_bind_negative_smb3signCtoHs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signCtoHd", test_session_bind_negative_smb3signCtoHd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signCtoGs", test_session_bind_negative_smb3signCtoGs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signCtoGd", test_session_bind_negative_smb3signCtoGd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signHtoCs", test_session_bind_negative_smb3signHtoCs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signHtoCd", test_session_bind_negative_smb3signHtoCd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signHtoGs", test_session_bind_negative_smb3signHtoGs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signHtoGd", test_session_bind_negative_smb3signHtoGd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoCs", test_session_bind_negative_smb3signGtoCs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoCd", test_session_bind_negative_smb3signGtoCd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoHs", test_session_bind_negative_smb3signGtoHs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoHd", test_session_bind_negative_smb3signGtoHd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneGtoCs", test_session_bind_negative_smb3sneGtoCs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneGtoCd", test_session_bind_negative_smb3sneGtoCd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneGtoHs", test_session_bind_negative_smb3sneGtoHs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneGtoHd", test_session_bind_negative_smb3sneGtoHd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneCtoGs", test_session_bind_negative_smb3sneCtoGs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneCtoGd", test_session_bind_negative_smb3sneCtoGd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneHtoGs", test_session_bind_negative_smb3sneHtoGs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3sneHtoGd", test_session_bind_negative_smb3sneHtoGd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signC30toGs", test_session_bind_negative_smb3signC30toGs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signC30toGd", test_session_bind_negative_smb3signC30toGd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signH2XtoGs", test_session_bind_negative_smb3signH2XtoGs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signH2XtoGd", test_session_bind_negative_smb3signH2XtoGd); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoC30s", test_session_bind_negative_smb3signGtoC30s); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoC30d", test_session_bind_negative_smb3signGtoC30d); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoH2Xs", test_session_bind_negative_smb3signGtoH2Xs); + torture_suite_add_1smb2_test(suite, "bind_negative_smb3signGtoH2Xd", test_session_bind_negative_smb3signGtoH2Xd); + torture_suite_add_1smb2_test(suite, "two_logoff", test_session_two_logoff); + torture_suite_add_1smb2_test(suite, "signing-hmac-sha-256", test_session_signing_hmac_sha_256); + torture_suite_add_1smb2_test(suite, "signing-aes-128-cmac", test_session_signing_aes_128_cmac); + torture_suite_add_1smb2_test(suite, "signing-aes-128-gmac", test_session_signing_aes_128_gmac); + torture_suite_add_1smb2_test(suite, "encryption-aes-128-ccm", test_session_encryption_aes_128_ccm); + torture_suite_add_1smb2_test(suite, "encryption-aes-128-gcm", test_session_encryption_aes_128_gcm); + torture_suite_add_1smb2_test(suite, "encryption-aes-256-ccm", test_session_encryption_aes_256_ccm); + torture_suite_add_1smb2_test(suite, "encryption-aes-256-gcm", test_session_encryption_aes_256_gcm); + torture_suite_add_1smb2_test(suite, "ntlmssp_bug14932", test_session_ntlmssp_bug14932); + + suite->description = talloc_strdup(suite, "SMB2-SESSION tests"); + + return suite; +} + +static bool test_session_require_sign_bug15397(struct torture_context *tctx, + struct smb2_tree *_tree) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + struct cli_credentials *_creds = samba_cmdline_get_creds(); + struct cli_credentials *creds = NULL; + struct smbcli_options options; + struct smb2_tree *tree = NULL; + uint8_t security_mode; + NTSTATUS status; + bool ok = true; + + /* + * Setup our own connection so we can control the signing flags + */ + + creds = cli_credentials_shallow_copy(tctx, _creds); + torture_assert(tctx, creds != NULL, "cli_credentials_shallow_copy"); + + options = _tree->session->transport->options; + options.client_guid = GUID_random(); + options.signing = SMB_SIGNING_IF_REQUIRED; + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + creds, + &tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "smb2_connect failed"); + + security_mode = smb2cli_session_security_mode(tree->session->smbXcli); + + torture_assert_int_equal_goto( + tctx, + security_mode, + SMB2_NEGOTIATE_SIGNING_REQUIRED | SMB2_NEGOTIATE_SIGNING_ENABLED, + ok, + done, + "Signing not required"); + +done: + return ok; +} + +struct torture_suite *torture_smb2_session_req_sign_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "session-require-signing"); + + torture_suite_add_1smb2_test(suite, "bug15397", + test_session_require_sign_bug15397); + + suite->description = talloc_strdup(suite, "SMB2-SESSION require signing tests"); + return suite; +} diff --git a/source4/torture/smb2/setinfo.c b/source4/torture/smb2/setinfo.c new file mode 100644 index 0000000..3b01b05 --- /dev/null +++ b/source4/torture/smb2/setinfo.c @@ -0,0 +1,410 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 setinfo individual test suite + + Copyright (C) Andrew Tridgell 2005 + + 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/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" + +static bool find_returned_ea(union smb_fileinfo *finfo2, + const char *eaname, + const char *eavalue) +{ + unsigned int i; + unsigned int num_eas = finfo2->all_eas.out.num_eas; + struct ea_struct *eas = finfo2->all_eas.out.eas; + + for (i = 0; i < num_eas; i++) { + if (eas[i].name.s == NULL) { + continue; + } + /* Windows capitalizes returned EA names. */ + if (strcasecmp_m(eas[i].name.s, eaname)) { + continue; + } + if (eavalue == NULL && eas[i].value.length == 0) { + /* Null value, found it ! */ + return true; + } + if (eas[i].value.length == strlen(eavalue) && + memcmp(eas[i].value.data, + eavalue, + strlen(eavalue)) == 0) { + return true; + } + } + return false; +} + +#define BASEDIR "" + +#define FAIL_UNLESS(__cond) \ + do { \ + if (__cond) {} else { \ + torture_result(tctx, TORTURE_FAIL, "%s) condition violated: %s\n", \ + __location__, #__cond); \ + ret = false; goto done; \ + } \ + } while(0) + +/* basic testing of all SMB2 setinfo calls + for each call we test that it succeeds, and where possible test + for consistency between the calls. +*/ +bool torture_smb2_setinfo(struct torture_context *tctx) +{ + struct smb2_tree *tree; + bool ret = true; + struct smb2_handle handle; + char *fname; + union smb_fileinfo finfo2; + union smb_setfileinfo sfinfo; + struct security_ace ace; + struct security_descriptor *sd; + struct dom_sid *test_sid; + NTSTATUS status, status2=NT_STATUS_OK; + const char *call_name; + time_t basetime = (time(NULL) - 86400) & ~1; + int n = time(NULL) % 100; + struct ea_struct ea; + + ZERO_STRUCT(handle); + + fname = talloc_asprintf(tctx, BASEDIR "fnum_test_%d.txt", n); + + if (!torture_smb2_connection(tctx, &tree)) { + return false; + } + +#define RECREATE_FILE(fname) do { \ + smb2_util_close(tree, handle); \ + status = smb2_create_complex_file(tctx, tree, fname, &handle); \ + if (!NT_STATUS_IS_OK(status)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) ERROR: open of %s failed (%s)\n", \ + __location__, fname, nt_errstr(status)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define RECREATE_BOTH do { \ + RECREATE_FILE(fname); \ + } while (0) + + RECREATE_BOTH; + +#define CHECK_CALL(call, rightstatus) do { \ + call_name = #call; \ + sfinfo.generic.level = RAW_SFILEINFO_ ## call; \ + sfinfo.generic.in.file.handle = handle; \ + status = smb2_setinfo_file(tree, &sfinfo); \ + if (!NT_STATUS_EQUAL(status, rightstatus)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) %s - %s (should be %s)\n", __location__, #call, \ + nt_errstr(status), nt_errstr(rightstatus)); \ + ret = false; \ + goto done; \ + } \ + } while (0) + +#define CHECK1(call) \ + do { if (NT_STATUS_IS_OK(status)) { \ + finfo2.generic.level = RAW_FILEINFO_ ## call; \ + finfo2.generic.in.file.handle = handle; \ + status2 = smb2_getinfo_file(tree, tctx, &finfo2); \ + if (!NT_STATUS_IS_OK(status2)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) %s - %s\n", __location__, #call, nt_errstr(status2)); \ + ret = false; \ + goto done; \ + } \ + }} while (0) + +#define CHECK_VALUE(call, stype, field, value) do { \ + CHECK1(call); \ + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(status2) && finfo2.stype.out.field != value) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) %s - %s/%s should be 0x%x - 0x%x\n", __location__, \ + call_name, #stype, #field, \ + (unsigned int)value, (unsigned int)finfo2.stype.out.field); \ + torture_smb2_all_info(tctx, tree, handle); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_TIME(call, stype, field, value) do { \ + CHECK1(call); \ + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(status2) && nt_time_to_unix(finfo2.stype.out.field) != value) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) %s - %s/%s should be 0x%x - 0x%x\n", __location__, \ + call_name, #stype, #field, \ + (unsigned int)value, \ + (unsigned int)nt_time_to_unix(finfo2.stype.out.field)); \ + torture_warning(tctx, "\t%s", timestring(tctx, value)); \ + torture_warning(tctx, "\t%s\n", nt_time_string(tctx, finfo2.stype.out.field)); \ + torture_smb2_all_info(tctx, tree, handle); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + + torture_smb2_all_info(tctx, tree, handle); + + torture_comment(tctx, "Test basic_information level\n"); + basetime += 86400; + unix_to_nt_time(&sfinfo.basic_info.in.create_time, basetime + 100); + unix_to_nt_time(&sfinfo.basic_info.in.access_time, basetime + 200); + unix_to_nt_time(&sfinfo.basic_info.in.write_time, basetime + 300); + unix_to_nt_time(&sfinfo.basic_info.in.change_time, basetime + 400); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_READONLY; + CHECK_CALL(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, create_time, basetime + 100); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, access_time, basetime + 200); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, write_time, basetime + 300); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, change_time, basetime + 400); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, attrib, FILE_ATTRIBUTE_READONLY); + + torture_comment(tctx, "a zero time means don't change\n"); + unix_to_nt_time(&sfinfo.basic_info.in.create_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.access_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.write_time, 0); + unix_to_nt_time(&sfinfo.basic_info.in.change_time, 0); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL; + CHECK_CALL(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, create_time, basetime + 100); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, access_time, basetime + 200); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, write_time, basetime + 300); + CHECK_TIME(SMB2_ALL_INFORMATION, all_info2, change_time, basetime + 400); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, attrib, FILE_ATTRIBUTE_NORMAL); + + torture_comment(tctx, "change the attribute\n"); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_HIDDEN; + CHECK_CALL(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, attrib, FILE_ATTRIBUTE_HIDDEN); + + torture_comment(tctx, "zero attrib means don't change\n"); + sfinfo.basic_info.in.attrib = 0; + CHECK_CALL(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, attrib, FILE_ATTRIBUTE_HIDDEN); + + torture_comment(tctx, "can't change a file to a directory\n"); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_DIRECTORY; + CHECK_CALL(BASIC_INFORMATION, NT_STATUS_INVALID_PARAMETER); + + torture_comment(tctx, "restore attribute\n"); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL; + CHECK_CALL(BASIC_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, attrib, FILE_ATTRIBUTE_NORMAL); + + torture_comment(tctx, "Test disposition_information level\n"); + sfinfo.disposition_info.in.delete_on_close = 1; + CHECK_CALL(DISPOSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, delete_pending, 1); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, nlink, 0); + + sfinfo.disposition_info.in.delete_on_close = 0; + CHECK_CALL(DISPOSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, delete_pending, 0); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, nlink, 1); + + torture_comment(tctx, "Test allocation_information level\n"); + sfinfo.allocation_info.in.alloc_size = 0; + CHECK_CALL(ALLOCATION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, size, 0); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, alloc_size, 0); + + sfinfo.allocation_info.in.alloc_size = 4096; + CHECK_CALL(ALLOCATION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, alloc_size, 4096); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, size, 0); + + torture_comment(tctx, "Test end_of_file_info level\n"); + sfinfo.end_of_file_info.in.size = 37; + CHECK_CALL(END_OF_FILE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, size, 37); + + sfinfo.end_of_file_info.in.size = 7; + CHECK_CALL(END_OF_FILE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, size, 7); + + torture_comment(tctx, "Test position_information level\n"); + sfinfo.position_information.in.position = 123456; + CHECK_CALL(POSITION_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(POSITION_INFORMATION, position_information, position, 123456); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, position, 123456); + + torture_comment(tctx, "Test mode_information level\n"); + sfinfo.mode_information.in.mode = 2; + CHECK_CALL(MODE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(MODE_INFORMATION, mode_information, mode, 2); + CHECK_VALUE(SMB2_ALL_INFORMATION, all_info2, mode, 2); + + sfinfo.mode_information.in.mode = 1; + CHECK_CALL(MODE_INFORMATION, NT_STATUS_INVALID_PARAMETER); + + sfinfo.mode_information.in.mode = 0; + CHECK_CALL(MODE_INFORMATION, NT_STATUS_OK); + CHECK_VALUE(MODE_INFORMATION, mode_information, mode, 0); + + torture_comment(tctx, "Test sec_desc level\n"); + ZERO_STRUCT(finfo2); + finfo2.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + CHECK1(SEC_DESC); + sd = finfo2.query_secdesc.out.sd; + + test_sid = dom_sid_parse_talloc(tctx, SID_NT_AUTHENTICATED_USERS); + ZERO_STRUCT(ace); + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.access_mask = SEC_STD_ALL; + ace.trustee = *test_sid; + status = security_descriptor_dacl_add(sd, &ace); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "add a new ACE to the DACL\n"); + + sfinfo.set_secdesc.in.secinfo_flags = finfo2.query_secdesc.in.secinfo_flags; + sfinfo.set_secdesc.in.sd = sd; + CHECK_CALL(SEC_DESC, NT_STATUS_OK); + FAIL_UNLESS(smb2_util_verify_sd(tctx, tree, handle, sd)); + + torture_comment(tctx, "remove it again\n"); + + status = security_descriptor_dacl_del(sd, test_sid); + CHECK_STATUS(status, NT_STATUS_OK); + + sfinfo.set_secdesc.in.secinfo_flags = finfo2.query_secdesc.in.secinfo_flags; + sfinfo.set_secdesc.in.sd = sd; + CHECK_CALL(SEC_DESC, NT_STATUS_OK); + FAIL_UNLESS(smb2_util_verify_sd(tctx, tree, handle, sd)); + + torture_comment(tctx, "Check zero length EA's behavior\n"); + + /* Set a new EA. */ + sfinfo.full_ea_information.in.eas.num_eas = 1; + ea.flags = 0; + ea.name.private_length = 6; + ea.name.s = "NewEA"; + ea.value = data_blob_string_const("testme"); + sfinfo.full_ea_information.in.eas.eas = &ea; + CHECK_CALL(FULL_EA_INFORMATION, NT_STATUS_OK); + + /* Does it still exist ? */ + finfo2.generic.level = RAW_FILEINFO_SMB2_ALL_EAS; + finfo2.generic.in.file.handle = handle; + finfo2.all_eas.in.continue_flags = 1; + status2 = smb2_getinfo_file(tree, tctx, &finfo2); + if (!NT_STATUS_IS_OK(status2)) { + torture_result(tctx, TORTURE_FAIL, "(%s) %s - %s\n", __location__, + "SMB2_ALL_EAS", nt_errstr(status2)); + ret = false; + goto done; + } + + /* Note on Windows EA name is returned capitalized. */ + if (!find_returned_ea(&finfo2, "NewEA", "testme")) { + torture_result(tctx, TORTURE_FAIL, "(%s) Missing EA 'NewEA'\n", __location__); + ret = false; + } + + /* Now zero it out (should delete it) */ + sfinfo.full_ea_information.in.eas.num_eas = 1; + ea.flags = 0; + ea.name.private_length = 6; + ea.name.s = "NewEA"; + ea.value = data_blob_null; + sfinfo.full_ea_information.in.eas.eas = &ea; + CHECK_CALL(FULL_EA_INFORMATION, NT_STATUS_OK); + + /* Does it still exist ? */ + finfo2.generic.level = RAW_FILEINFO_SMB2_ALL_EAS; + finfo2.generic.in.file.handle = handle; + finfo2.all_eas.in.continue_flags = 1; + status2 = smb2_getinfo_file(tree, tctx, &finfo2); + if (!NT_STATUS_IS_OK(status2)) { + torture_result(tctx, TORTURE_FAIL, "(%s) %s - %s\n", __location__, + "SMB2_ALL_EAS", nt_errstr(status2)); + ret = false; + goto done; + } + + if (find_returned_ea(&finfo2, "NewEA", NULL)) { + torture_result(tctx, TORTURE_FAIL, "(%s) EA 'NewEA' should be deleted\n", __location__); + ret = false; + } + + /* Set a zero length EA. */ + sfinfo.full_ea_information.in.eas.num_eas = 1; + ea.flags = 0; + ea.name.private_length = 6; + ea.name.s = "ZeroEA"; + ea.value = data_blob_null; + sfinfo.full_ea_information.in.eas.eas = &ea; + CHECK_CALL(FULL_EA_INFORMATION, NT_STATUS_OK); + + /* Does it still exist ? */ + finfo2.generic.level = RAW_FILEINFO_SMB2_ALL_EAS; + finfo2.generic.in.file.handle = handle; + finfo2.all_eas.in.continue_flags = 1; + status2 = smb2_getinfo_file(tree, tctx, &finfo2); + if (!NT_STATUS_IS_OK(status2)) { + torture_result(tctx, TORTURE_FAIL, "(%s) %s - %s\n", __location__, + "SMB2_ALL_EAS", nt_errstr(status2)); + ret = false; + goto done; + } + + /* Over SMB2 ZeroEA should not exist. */ + if (!find_returned_ea(&finfo2, "EAONE", "VALUE1")) { + torture_result(tctx, TORTURE_FAIL, "(%s) Missing EA 'EAONE'\n", __location__); + ret = false; + } + if (!find_returned_ea(&finfo2, "SECONDEA", "ValueTwo")) { + torture_result(tctx, TORTURE_FAIL, "(%s) Missing EA 'SECONDEA'\n", __location__); + ret = false; + } + if (find_returned_ea(&finfo2, "ZeroEA", NULL)) { + torture_result(tctx, TORTURE_FAIL, "(%s) Found null EA 'ZeroEA'\n", __location__); + ret = false; + } + +done: + status = smb2_util_close(tree, handle); + if (NT_STATUS_IS_ERR(status)) { + torture_warning(tctx, "Failed to delete %s - %s\n", fname, nt_errstr(status)); + } + smb2_util_unlink(tree, fname); + + return ret; +} + + diff --git a/source4/torture/smb2/sharemode.c b/source4/torture/smb2/sharemode.c new file mode 100644 index 0000000..97381b5 --- /dev/null +++ b/source4/torture/smb2/sharemode.c @@ -0,0 +1,755 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 sharemodes + + Copyright (C) Christof Schmitt 2017 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/security/security.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "lib/util/smb_strtox.h" +#include <tevent.h> + +#define BASEDIRHOLD "sharemode_hold_test" + +struct hold_sharemode_info { + const char *sharemode; + const char *filename; + struct smb2_handle handle; +} hold_sharemode_table[] = { + { + .sharemode = "", + .filename = BASEDIRHOLD "\\N", + }, + { + .sharemode = "R", + .filename = BASEDIRHOLD "\\R", + }, + { + .sharemode = "W", + .filename = BASEDIRHOLD "\\W", + }, + { + .sharemode = "D", + .filename = BASEDIRHOLD "\\D", + }, + { + .sharemode = "RW", + .filename = BASEDIRHOLD "\\RW", + }, + { + .sharemode = "RD", + .filename = BASEDIRHOLD "\\RD", + }, + { + .sharemode = "WD", + .filename = BASEDIRHOLD "\\WD", + }, + { + .sharemode = "RWD", + .filename = BASEDIRHOLD "\\RWD", + }, +}; + +static void signal_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct torture_context *tctx = private_data; + + torture_comment(tctx, "Received signal %d\n", signum); +} + +/* + * Used for manual testing of sharemodes - especially interaction with + * other filesystems (such as NFS and local access). The scenario is + * that this test holds files open and then concurrent access to the same + * files outside of Samba can be tested. + */ +bool torture_smb2_hold_sharemode(struct torture_context *tctx) +{ + struct tevent_context *ev = tctx->ev; + struct smb2_tree *tree = NULL; + struct smb2_handle dir_handle; + struct tevent_signal *s; + NTSTATUS status; + bool ret = true; + int i; + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + s = tevent_add_signal(ev, tctx, SIGINT, 0, signal_handler, tctx); + torture_assert_not_null_goto(tctx, s, ret, done, + "Error registering signal handler."); + + torture_comment(tctx, "Setting up open files with sharemodes in %s\n", + BASEDIRHOLD); + + status = torture_smb2_testdir(tree, BASEDIRHOLD, &dir_handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Error creating directory."); + + for (i = 0; i < ARRAY_SIZE(hold_sharemode_table); i++) { + struct hold_sharemode_info *info = &hold_sharemode_table[i]; + struct smb2_create create = { 0 }; + + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.alloc_size = 0; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = + smb2_util_share_access(info->sharemode); + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.create_options = 0; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.security_flags = 0; + create.in.fname = info->filename; + create.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + torture_comment(tctx, "opening %s\n", info->filename); + + status = smb2_create(tree, tctx, &create); + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + info->handle = create.out.file.handle; + } + + torture_comment(tctx, "Waiting for SIGINT (ctrl-c)\n"); + tevent_loop_wait(ev); + + torture_comment(tctx, "Closing and deleting files\n"); + + for (i = 0; i < ARRAY_SIZE(hold_sharemode_table); i++) { + struct hold_sharemode_info *info = &hold_sharemode_table[i]; + + union smb_setfileinfo sfinfo = { }; + + sfinfo.disposition_info.in.delete_on_close = 1; + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.generic.in.file.handle = info->handle; + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "SETINFO failed\n"); + + status = smb2_util_close(tree, info->handle); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + torture_comment(tctx, "File %s not found, could have " + "been deleted outside of SMB\n", + info->filename); + continue; + } + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE failed\n"); +} + +done: + smb2_deltree(tree, BASEDIRHOLD); + return ret; +} + +/* + * Used for manual testing of sharemodes, especially interaction with + * file systems that can enforce sharemodes. The scenario here is that + * a file is already open outside of Samba with a sharemode and this + * can be used to test accessing the same file from Samba. + */ +bool torture_smb2_check_sharemode(struct torture_context *tctx) +{ + const char *sharemode_string, *access_string, *filename, *operation; + uint32_t sharemode, access; + struct smb2_tree *tree; + struct smb2_create create = { 0 }; + NTSTATUS status; + bool ret = true; + int error = 0; + + sharemode_string = torture_setting_string(tctx, "sharemode", "RWD"); + sharemode = smb2_util_share_access(sharemode_string); + + access_string = torture_setting_string(tctx, "access", "0xf01ff"); + access = smb_strtoul(access_string, NULL, 0, &error, SMB_STR_STANDARD); + if (error != 0) { + torture_comment(tctx, "Initializing access failed.\n"); + return false; + } + + filename = torture_setting_string(tctx, "filename", "testfile"); + operation = torture_setting_string(tctx, "operation", "WD"); + + if (!torture_smb2_connection(tctx, &tree)) { + torture_comment(tctx, "Initializing smb2 connection failed.\n"); + return false; + } + + create.in.desired_access = access; + create.in.alloc_size = 0; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = sharemode; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.create_options = 0; + create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create.in.security_flags = 0; + create.in.fname = filename; + create.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + create.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE failed\n"); + + if (strchr(operation, 'R')) { + struct smb2_read read = { 0 }; + + read.in.file.handle = create.out.file.handle; + read.in.offset = 0; + read.in.length = 1; + + status = smb2_read(tree, tctx, &read); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "READ failed\n"); + } + + if (strchr(operation, 'W')) { + char buf[1]; + status = smb2_util_write(tree, create.out.file.handle, + &buf, 0, sizeof(buf)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "WRITE failed\n"); + } + + if (strchr(operation, 'D')) { + union smb_setfileinfo sfinfo = { }; + + sfinfo.disposition_info.in.delete_on_close = 1; + sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION; + sfinfo.generic.in.file.handle = create.out.file.handle; + + status = smb2_setinfo_file(tree, &sfinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "SETINFO failed\n"); + + status = smb2_util_close(tree, create.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE failed\n"); + } + +done: + return ret; +} + +struct sharemode_info { + const char *sharemode; + uint32_t access_mask; + bool expect_ok; +} sharemode_table[] = { + + /* + * Basic tests, check each permission bit against every + * possible sharemode combination. + */ + + { "R", SEC_FILE_READ_DATA, true, }, + { "R", SEC_FILE_WRITE_DATA, false, }, + { "R", SEC_FILE_APPEND_DATA, false, }, + { "R", SEC_FILE_READ_EA, true, }, + { "R", SEC_FILE_WRITE_EA, true, }, + { "R", SEC_FILE_EXECUTE, true, }, + { "R", SEC_FILE_READ_ATTRIBUTE, true, }, + { "R", SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "R", SEC_STD_DELETE, false, }, + { "R", SEC_STD_READ_CONTROL, true, }, + { "R", SEC_STD_WRITE_DAC, true, }, + { "R", SEC_STD_WRITE_OWNER, true, }, + { "R", SEC_STD_SYNCHRONIZE, true, }, + + { "W", SEC_FILE_READ_DATA, false }, + { "W", SEC_FILE_WRITE_DATA, true, }, + { "W", SEC_FILE_APPEND_DATA, true, }, + { "W", SEC_FILE_READ_EA, true, }, + { "W", SEC_FILE_WRITE_EA, true, }, + { "W", SEC_FILE_EXECUTE, false, }, + { "W", SEC_FILE_READ_ATTRIBUTE, true, }, + { "W", SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "W", SEC_STD_DELETE, false, }, + { "W", SEC_STD_READ_CONTROL, true, }, + { "W", SEC_STD_WRITE_DAC, true, }, + { "W", SEC_STD_WRITE_OWNER, true, }, + { "W", SEC_STD_SYNCHRONIZE, true, }, + + { "D", SEC_FILE_READ_DATA, false }, + { "D", SEC_FILE_WRITE_DATA, false }, + { "D", SEC_FILE_APPEND_DATA, false }, + { "D", SEC_FILE_READ_EA, true, }, + { "D", SEC_FILE_WRITE_EA, true, }, + { "D", SEC_FILE_EXECUTE, false, }, + { "D", SEC_FILE_READ_ATTRIBUTE, true, }, + { "D", SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "D", SEC_STD_DELETE, true, }, + { "D", SEC_STD_READ_CONTROL, true, }, + { "D", SEC_STD_WRITE_DAC, true, }, + { "D", SEC_STD_WRITE_OWNER, true, }, + { "D", SEC_STD_SYNCHRONIZE, true, }, + + { "RW", SEC_FILE_READ_DATA, true, }, + { "RW", SEC_FILE_WRITE_DATA, true, }, + { "RW", SEC_FILE_APPEND_DATA, true, }, + { "RW", SEC_FILE_READ_EA, true, }, + { "RW", SEC_FILE_WRITE_EA, true, }, + { "RW", SEC_FILE_EXECUTE, true, }, + { "RW", SEC_FILE_READ_ATTRIBUTE, true, }, + { "RW", SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "RW", SEC_STD_DELETE, false, }, + { "RW", SEC_STD_READ_CONTROL, true, }, + { "RW", SEC_STD_WRITE_DAC, true, }, + { "RW", SEC_STD_WRITE_OWNER, true, }, + { "RW", SEC_STD_SYNCHRONIZE, true, }, + + { "RD", SEC_FILE_READ_DATA, true, }, + { "RD", SEC_FILE_WRITE_DATA, false, }, + { "RD", SEC_FILE_APPEND_DATA, false, }, + { "RD", SEC_FILE_READ_EA, true, }, + { "RD", SEC_FILE_WRITE_EA, true, }, + { "RD", SEC_FILE_EXECUTE, true, }, + { "RD", SEC_FILE_READ_ATTRIBUTE, true, }, + { "RD", SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "RD", SEC_STD_DELETE, true, }, + { "RD", SEC_STD_READ_CONTROL, true, }, + { "RD", SEC_STD_WRITE_DAC, true, }, + { "RD", SEC_STD_WRITE_OWNER, true, }, + { "RD", SEC_STD_SYNCHRONIZE, true, }, + + { "WD", SEC_FILE_READ_DATA, false }, + { "WD", SEC_FILE_WRITE_DATA, true, }, + { "WD", SEC_FILE_APPEND_DATA, true, }, + { "WD", SEC_FILE_READ_EA, true }, + { "WD", SEC_FILE_WRITE_EA, true, }, + { "WD", SEC_FILE_EXECUTE, false }, + { "WD", SEC_FILE_READ_ATTRIBUTE, true, }, + { "WD", SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "WD", SEC_STD_DELETE, true, }, + { "WD", SEC_STD_READ_CONTROL, true, }, + { "WD", SEC_STD_WRITE_DAC, true, }, + { "WD", SEC_STD_WRITE_OWNER, true, }, + { "WD", SEC_STD_SYNCHRONIZE, true, }, + + { "RWD", SEC_FILE_READ_DATA, true }, + { "RWD", SEC_FILE_WRITE_DATA, true, }, + { "RWD", SEC_FILE_APPEND_DATA, true, }, + { "RWD", SEC_FILE_READ_EA, true }, + { "RWD", SEC_FILE_WRITE_EA, true, }, + { "RWD", SEC_FILE_EXECUTE, true, }, + { "RWD", SEC_FILE_READ_ATTRIBUTE, true, }, + { "RWD", SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "RWD", SEC_STD_DELETE, true, }, + { "RWD", SEC_STD_READ_CONTROL, true, }, + { "RWD", SEC_STD_WRITE_DAC, true, }, + { "RWD", SEC_STD_WRITE_OWNER, true, }, + { "RWD", SEC_STD_SYNCHRONIZE, true, }, + + /* + * Some more interesting cases. Always request READ or WRITE + * access, as that will trigger the opening of a file + * description in Samba. This especially useful for file + * systems that enforce share modes on open file descriptors. + */ + + { "R", SEC_FILE_READ_DATA, true, }, + { "R", SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, false, }, + { "R", SEC_FILE_READ_DATA|SEC_FILE_APPEND_DATA, false, }, + { "R", SEC_FILE_READ_DATA|SEC_FILE_READ_EA, true, }, + { "R", SEC_FILE_READ_DATA|SEC_FILE_WRITE_EA, true, }, + { "R", SEC_FILE_READ_DATA|SEC_FILE_EXECUTE, true, }, + { "R", SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE, true, }, + { "R", SEC_FILE_READ_DATA|SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "R", SEC_FILE_READ_DATA|SEC_STD_DELETE, false, }, + { "R", SEC_FILE_READ_DATA|SEC_STD_READ_CONTROL, true, }, + { "R", SEC_FILE_READ_DATA|SEC_STD_WRITE_DAC, true, }, + { "R", SEC_FILE_READ_DATA|SEC_STD_WRITE_OWNER, true, }, + { "R", SEC_FILE_READ_DATA|SEC_STD_SYNCHRONIZE, true, }, + + { "W", SEC_FILE_WRITE_DATA|SEC_FILE_READ_DATA, false, }, + { "W", SEC_FILE_WRITE_DATA, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_FILE_APPEND_DATA, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_FILE_READ_EA, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_FILE_WRITE_EA, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_FILE_EXECUTE, false, }, + { "W", SEC_FILE_WRITE_DATA|SEC_FILE_READ_ATTRIBUTE, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_STD_DELETE, false, }, + { "W", SEC_FILE_WRITE_DATA|SEC_STD_READ_CONTROL, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_STD_WRITE_DAC, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_STD_WRITE_OWNER, true, }, + { "W", SEC_FILE_WRITE_DATA|SEC_STD_SYNCHRONIZE, true, }, + + { "RW", SEC_FILE_READ_DATA, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_FILE_APPEND_DATA, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_FILE_READ_EA, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_FILE_WRITE_EA, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_FILE_EXECUTE, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_STD_DELETE, false, }, + { "RW", SEC_FILE_READ_DATA|SEC_STD_READ_CONTROL, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_STD_WRITE_DAC, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_STD_WRITE_OWNER, true, }, + { "RW", SEC_FILE_READ_DATA|SEC_STD_SYNCHRONIZE, true, }, + + { "RD", SEC_FILE_READ_DATA, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, false, }, + { "RD", SEC_FILE_READ_DATA|SEC_FILE_APPEND_DATA, false, }, + { "RD", SEC_FILE_READ_DATA|SEC_FILE_READ_EA, true }, + { "RD", SEC_FILE_READ_DATA|SEC_FILE_WRITE_EA, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_FILE_EXECUTE, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_STD_DELETE, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_STD_READ_CONTROL, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_STD_WRITE_DAC, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_STD_WRITE_OWNER, true, }, + { "RD", SEC_FILE_READ_DATA|SEC_STD_SYNCHRONIZE, true, }, + + { "WD", SEC_FILE_WRITE_DATA|SEC_FILE_READ_DATA, false }, + { "WD", SEC_FILE_WRITE_DATA, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_FILE_APPEND_DATA, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_FILE_READ_EA, true }, + { "WD", SEC_FILE_WRITE_DATA|SEC_FILE_WRITE_EA, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_FILE_EXECUTE, false }, + { "WD", SEC_FILE_WRITE_DATA|SEC_FILE_READ_ATTRIBUTE, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_STD_DELETE, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_STD_READ_CONTROL, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_STD_WRITE_DAC, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_STD_WRITE_OWNER, true, }, + { "WD", SEC_FILE_WRITE_DATA|SEC_STD_SYNCHRONIZE, true, }, + + { "RWD", SEC_FILE_READ_DATA, true }, + { "RWD", SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_FILE_APPEND_DATA, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_FILE_READ_EA, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_FILE_WRITE_EA, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_FILE_EXECUTE, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_FILE_READ_ATTRIBUTE, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_FILE_WRITE_ATTRIBUTE, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_STD_DELETE, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_STD_READ_CONTROL, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_STD_WRITE_DAC, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_STD_WRITE_OWNER, true, }, + { "RWD", SEC_FILE_READ_DATA|SEC_STD_SYNCHRONIZE, true, }, +}; + +/* + * Test conflicting sharemodes through SMB2: First open takes a + * sharemode, second open with potentially conflicting access. + */ +static bool test_smb2_sharemode_access(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = "test_sharemode"; + NTSTATUS status; + bool ret = true; + int i; + + for (i = 0; i < ARRAY_SIZE(sharemode_table); i++) { + struct sharemode_info *info = &sharemode_table[i]; + struct smb2_create create1 = { 0 }, create2 = { 0 }; + NTSTATUS expected_status; + + torture_comment(tctx, "index %3d, sharemode %3s, " + "access mask 0x%06x\n", + i, info->sharemode, info->access_mask); + + create1.in.desired_access = SEC_RIGHTS_FILE_ALL; + create1.in.alloc_size = 0; + create1.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create1.in.share_access = + smb2_util_share_access(info->sharemode); + create1.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create1.in.create_options = 0; + create1.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create1.in.fname = fname; + create1.in.security_flags = 0; + create1.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + create1.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + status = smb2_create(tree1, tctx, &create1); + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + create2.in.desired_access = info->access_mask; + create2.in.alloc_size = 0; + create2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create2.in.create_disposition = NTCREATEX_DISP_OPEN; + create2.in.create_options = 0; + create2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create2.in.fname = fname; + create2.in.security_flags = 0; + create2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + create2.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + status = smb2_create(tree2, tctx, &create2); + expected_status = info->expect_ok ? + NT_STATUS_OK : NT_STATUS_SHARING_VIOLATION; + torture_assert_ntstatus_equal_goto(tctx, status, + expected_status, ret, + done, "Unexpected status on " + "second create.\n"); + + status = smb2_util_close(tree1, create1.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to close " + "first handle.\n"); + + if (info->expect_ok) { + status = smb2_util_close(tree2, create2.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to close " + "second handle.\n"); + } + } + +done: + smb2_util_unlink(tree1, fname); + return ret; +} + +/* + * Test conflicting sharemodes through SMB2: First open file with + * different access masks, second open requests potentially conflicting + * sharemode. + */ +static bool test_smb2_access_sharemode(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + const char *fname = "test_sharemode"; + NTSTATUS status; + bool ret = true; + int i; + + for (i = 0; i < ARRAY_SIZE(sharemode_table); i++) { + struct sharemode_info *info = &sharemode_table[i]; + struct smb2_create create1 = { 0 }, create2 = { 0 }; + NTSTATUS expected_status; + + torture_comment(tctx, "index %3d, access mask 0x%06x, " + "sharemode %3s\n", + i, info->access_mask, info->sharemode); + + create1.in.desired_access = info->access_mask; + create1.in.alloc_size = 0; + create1.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create1.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create1.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create1.in.create_options = 0; + create1.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create1.in.fname = fname; + create1.in.security_flags = 0; + create1.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + create1.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + status = smb2_create(tree1, tctx, &create1); + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + create2.in.desired_access = SEC_RIGHTS_FILE_ALL; + create2.in.alloc_size = 0; + create2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create2.in.share_access = + smb2_util_share_access(info->sharemode); + create2.in.create_disposition = NTCREATEX_DISP_OPEN; + create2.in.create_options = 0; + create2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + create2.in.fname = fname; + create2.in.security_flags = 0; + create2.in.create_flags = NTCREATEX_FLAGS_EXTENDED; + create2.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + + status = smb2_create(tree2, tctx, &create2); + + expected_status = info->expect_ok ? + NT_STATUS_OK : NT_STATUS_SHARING_VIOLATION; + torture_assert_ntstatus_equal_goto(tctx, status, + expected_status, ret, + done, "Unexpected status on " + "second create.\n"); + + status = smb2_util_close(tree1, create1.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to close " + "first handle.\n"); + + if (info->expect_ok) { + status = smb2_util_close(tree2, create2.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "Failed to close " + "second handle.\n"); + } + } + +done: + smb2_util_unlink(tree1, fname); + return ret; +} + +/* + * Test initial stat open with share nothing doesn't trigger SHARING_VIOLTION + * errors. + */ +static bool test_smb2_bug14375(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "test_bug14375"; + struct smb2_create cr1; + struct smb2_create cr2; + struct smb2_create cr3; + NTSTATUS status; + bool ret = true; + + smb2_util_unlink(tree, fname); + + cr1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_ATTRIBUTE, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_NONE, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + cr2 = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + cr3 = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr3); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + status = smb2_util_close(tree, cr1.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE file failed\n"); + status = smb2_util_close(tree, cr2.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE file failed\n"); + status = smb2_util_close(tree, cr3.out.file.handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE file failed\n"); + + cr1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + cr2 = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_ATTRIBUTE, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_NONE, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + + cr3 = (struct smb2_create) { + .in.desired_access = SEC_FILE_READ_DATA, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS, + .in.fname = fname, + }; + + status = smb2_create(tree, tctx, &cr3); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE file failed\n"); + +done: + smb2_util_close(tree, cr1.out.file.handle); + smb2_util_close(tree, cr2.out.file.handle); + smb2_util_close(tree, cr3.out.file.handle); + smb2_util_unlink(tree, fname); + return ret; +} + +struct torture_suite *torture_smb2_sharemode_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "sharemode"); + + torture_suite_add_2smb2_test(suite, "sharemode-access", + test_smb2_sharemode_access); + torture_suite_add_2smb2_test(suite, "access-sharemode", + test_smb2_access_sharemode); + torture_suite_add_1smb2_test(suite, "bug14375", + test_smb2_bug14375); + + suite->description = talloc_strdup(suite, "SMB2-SHAREMODE tests"); + + return suite; +} diff --git a/source4/torture/smb2/smb2.c b/source4/torture/smb2/smb2.c new file mode 100644 index 0000000..8621f09 --- /dev/null +++ b/source4/torture/smb2/smb2.c @@ -0,0 +1,229 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester + Copyright (C) Jelmer Vernooij 2006 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" + +#include "torture/smbtorture.h" +#include "torture/smb2/proto.h" +#include "../lib/util/dlinklist.h" + +static bool wrap_simple_1smb2_test(struct torture_context *torture_ctx, + struct torture_tcase *tcase, + struct torture_test *test) +{ + bool (*fn) (struct torture_context *, struct smb2_tree *); + bool ret; + struct smb2_tree *tree1; + TALLOC_CTX *mem_ctx = talloc_new(torture_ctx); + + if (!torture_smb2_connection(torture_ctx, &tree1)) { + torture_fail(torture_ctx, + "Establishing SMB2 connection failed\n"); + return false; + } + + /* + * This is a trick: + * The test might close the connection. If we steal the tree context + * before that and free the parent instead of tree directly, we avoid + * a double free error. + */ + talloc_steal(mem_ctx, tree1); + + fn = test->fn; + + ret = fn(torture_ctx, tree1); + + talloc_free(mem_ctx); + + return ret; +} + +struct torture_test *torture_suite_add_1smb2_test(struct torture_suite *suite, + const char *name, + bool (*run)(struct torture_context *, + struct smb2_tree *)) +{ + struct torture_test *test; + struct torture_tcase *tcase; + + tcase = torture_suite_add_tcase(suite, name); + + test = talloc(tcase, struct torture_test); + + test->name = talloc_strdup(test, name); + test->description = NULL; + test->run = wrap_simple_1smb2_test; + test->fn = run; + test->dangerous = false; + + DLIST_ADD_END(tcase->tests, test); + + return test; +} + + +static bool wrap_simple_2smb2_test(struct torture_context *torture_ctx, + struct torture_tcase *tcase, + struct torture_test *test) +{ + bool (*fn) (struct torture_context *, struct smb2_tree *, struct smb2_tree *); + bool ret = false; + + struct smb2_tree *tree1; + struct smb2_tree *tree2; + TALLOC_CTX *mem_ctx = talloc_new(torture_ctx); + + if (!torture_smb2_connection(torture_ctx, &tree1)) { + torture_fail(torture_ctx, + "Establishing SMB2 connection failed\n"); + goto done; + } + + talloc_steal(mem_ctx, tree1); + + if (!torture_smb2_connection(torture_ctx, &tree2)) { + torture_fail(torture_ctx, + "Establishing SMB2 connection failed\n"); + goto done; + } + + talloc_steal(mem_ctx, tree2); + + fn = test->fn; + + ret = fn(torture_ctx, tree1, tree2); + +done: + /* the test may already have closed some of the connections */ + talloc_free(mem_ctx); + + return ret; +} + + +struct torture_test *torture_suite_add_2smb2_test(struct torture_suite *suite, + const char *name, + bool (*run)(struct torture_context *, + struct smb2_tree *, + struct smb2_tree *)) +{ + struct torture_test *test; + struct torture_tcase *tcase; + + tcase = torture_suite_add_tcase(suite, name); + + test = talloc(tcase, struct torture_test); + + test->name = talloc_strdup(test, name); + test->description = NULL; + test->run = wrap_simple_2smb2_test; + test->fn = run; + test->dangerous = false; + + DLIST_ADD_END(tcase->tests, test); + + return test; +} + +NTSTATUS torture_smb2_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "smb2"); + torture_suite_add_simple_test(suite, "connect", torture_smb2_connect); + torture_suite_add_suite(suite, torture_smb2_scan_init(suite)); + torture_suite_add_suite(suite, torture_smb2_getinfo_init(suite)); + torture_suite_add_simple_test(suite, "setinfo", torture_smb2_setinfo); + torture_suite_add_suite(suite, torture_smb2_lock_init(suite)); + torture_suite_add_suite(suite, torture_smb2_read_init(suite)); + torture_suite_add_suite(suite, torture_smb2_aio_delay_init(suite)); + torture_suite_add_suite(suite, torture_smb2_bench_init(suite)); + torture_suite_add_suite(suite, torture_smb2_create_init(suite)); + torture_suite_add_suite(suite, torture_smb2_twrp_init(suite)); + torture_suite_add_suite(suite, torture_smb2_fileid_init(suite)); + torture_suite_add_suite(suite, torture_smb2_acls_init(suite)); + torture_suite_add_suite(suite, torture_smb2_acls_non_canonical_init(suite)); + torture_suite_add_suite(suite, torture_smb2_notify_init(suite)); + torture_suite_add_suite(suite, torture_smb2_notify_inotify_init(suite)); + torture_suite_add_suite(suite, + torture_smb2_notify_disabled_init(suite)); + torture_suite_add_suite(suite, torture_smb2_durable_open_init(suite)); + torture_suite_add_suite(suite, + torture_smb2_durable_open_disconnect_init(suite)); + torture_suite_add_suite(suite, + torture_smb2_durable_v2_open_init(suite)); + torture_suite_add_suite(suite, + torture_smb2_durable_v2_delay_init(suite)); + torture_suite_add_suite(suite, torture_smb2_dir_init(suite)); + torture_suite_add_suite(suite, torture_smb2_lease_init(suite)); + torture_suite_add_suite(suite, torture_smb2_compound_init(suite)); + torture_suite_add_suite(suite, torture_smb2_compound_find_init(suite)); + torture_suite_add_suite(suite, torture_smb2_compound_async_init(suite)); + torture_suite_add_suite(suite, torture_smb2_oplocks_init(suite)); + torture_suite_add_suite(suite, torture_smb2_kernel_oplocks_init(suite)); + torture_suite_add_suite(suite, torture_smb2_streams_init(suite)); + torture_suite_add_suite(suite, torture_smb2_ioctl_init(suite)); + torture_suite_add_simple_test(suite, "set-sparse-ioctl", + test_ioctl_set_sparse); + torture_suite_add_simple_test(suite, "zero-data-ioctl", + test_ioctl_zero_data); + torture_suite_add_simple_test(suite, "ioctl-on-stream", + test_ioctl_alternate_data_stream); + torture_suite_add_suite(suite, torture_smb2_rename_init(suite)); + torture_suite_add_suite(suite, torture_smb2_sharemode_init(suite)); + torture_suite_add_1smb2_test(suite, "hold-oplock", test_smb2_hold_oplock); + torture_suite_add_suite(suite, torture_smb2_session_init(suite)); + torture_suite_add_suite(suite, torture_smb2_session_req_sign_init(suite)); + torture_suite_add_suite(suite, torture_smb2_replay_init(suite)); + torture_suite_add_simple_test(suite, "dosmode", torture_smb2_dosmode); + torture_suite_add_simple_test(suite, "async_dosmode", torture_smb2_async_dosmode); + torture_suite_add_simple_test(suite, "maxfid", torture_smb2_maxfid); + torture_suite_add_simple_test(suite, "hold-sharemode", + torture_smb2_hold_sharemode); + torture_suite_add_simple_test(suite, "check-sharemode", + torture_smb2_check_sharemode); + torture_suite_add_suite(suite, torture_smb2_crediting_init(suite)); + + torture_suite_add_suite(suite, torture_smb2_doc_init(suite)); + torture_suite_add_suite(suite, torture_smb2_multichannel_init(suite)); + torture_suite_add_suite(suite, torture_smb2_samba3misc_init(suite)); + torture_suite_add_suite(suite, torture_smb2_timestamps_init(suite)); + torture_suite_add_suite(suite, torture_smb2_timestamp_resolution_init(suite)); + torture_suite_add_1smb2_test(suite, "openattr", torture_smb2_openattrtest); + torture_suite_add_1smb2_test(suite, "winattr", torture_smb2_winattrtest); + torture_suite_add_1smb2_test(suite, "sdread", torture_smb2_sdreadtest); + torture_suite_add_suite(suite, torture_smb2_readwrite_init(suite)); + torture_suite_add_1smb2_test(suite, "maximum_allowed", torture_smb2_maximum_allowed); + torture_suite_add_1smb2_test(suite, "mangle", torture_smb2_mangle); + torture_suite_add_1smb2_test(suite, "tcon", run_tcon_test); + torture_suite_add_1smb2_test(suite, "mkdir", torture_smb2_mkdir); + + torture_suite_add_suite(suite, torture_smb2_charset(suite)); + torture_suite_add_1smb2_test(suite, "secleak", torture_smb2_sec_leak); + torture_suite_add_1smb2_test(suite, "session-id", run_sessidtest); + torture_suite_add_suite(suite, torture_smb2_deny_init(suite)); + torture_suite_add_suite(suite, torture_smb2_ea(suite)); + torture_suite_add_suite(suite, torture_smb2_create_no_streams_init(suite)); + + suite->description = talloc_strdup(suite, "SMB2-specific tests"); + + torture_register_suite(ctx, suite); + + return NT_STATUS_OK; +} diff --git a/source4/torture/smb2/streams.c b/source4/torture/smb2/streams.c new file mode 100644 index 0000000..f18048f --- /dev/null +++ b/source4/torture/smb2/streams.c @@ -0,0 +1,2424 @@ +/* + Unix SMB/CIFS implementation. + + test alternate data streams + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +#include "smb_constants.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" + +#include "system/filesys.h" +#include "system/locale.h" +#include "lib/util/tsort.h" + +#define DNAME "teststreams" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect value %s=%d - should be %d\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +#define CHECK_NTTIME(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect value %s=%llu - should be %llu\n", \ + __location__, #v, (unsigned long long)v, \ + (unsigned long long)correct); \ + ret = false; \ + }} while (0) + +#define CHECK_STR(v, correct) do { \ + bool ok; \ + if ((v) && !(correct)) { \ + ok = false; \ + } else if (!(v) && (correct)) { \ + ok = false; \ + } else if (!(v) && !(correct)) { \ + ok = true; \ + } else if (strcmp((v), (correct)) == 0) { \ + ok = true; \ + } else { \ + ok = false; \ + } \ + if (!ok) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect value %s='%s' - " \ + "should be '%s'\n", \ + __location__, #v, (v)?(v):"NULL", \ + (correct)?(correct):"NULL"); \ + ret = false; \ + }} while (0) + + +static int qsort_string(char * const *s1, char * const *s2) +{ + return strcmp(*s1, *s2); +} + +static int qsort_stream(const struct stream_struct * s1, const struct stream_struct *s2) +{ + return strcmp(s1->stream_name.s, s2->stream_name.s); +} + +static bool check_stream(struct torture_context *tctx, + struct smb2_tree *tree, + const char *location, + TALLOC_CTX *mem_ctx, + const char *fname, + const char *sname, + const char *value) +{ + struct smb2_handle handle; + struct smb2_create create; + struct smb2_read r; + NTSTATUS status; + const char *full_name; + + full_name = talloc_asprintf(mem_ctx, "%s:%s", fname, sname); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_OPEN; + create.in.fname = full_name; + + status = smb2_create(tree, mem_ctx, &create); + if (!NT_STATUS_IS_OK(status)) { + if (value == NULL) { + return true; + } else { + torture_comment(tctx, "Unable to open stream %s\n", + full_name); + return false; + } + } + + handle = create.out.file.handle; + if (value == NULL) { + return true; + } + + + ZERO_STRUCT(r); + r.in.file.handle = handle; + r.in.length = strlen(value)+11; + r.in.offset = 0; + + status = smb2_read(tree, tree, &r); + + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "(%s) Failed to read %lu bytes from " + "stream '%s'\n", location, (long)strlen(value), full_name); + return false; + } + + if (memcmp(r.out.data.data, value, strlen(value)) != 0) { + torture_comment(tctx, "(%s) Bad data in stream\n", location); + return false; + } + + smb2_util_close(tree, handle); + return true; +} + +static bool check_stream_list(struct smb2_tree *tree, + struct torture_context *tctx, + const char *fname, + unsigned int num_exp, + const char **exp, + struct smb2_handle h) +{ + union smb_fileinfo finfo; + NTSTATUS status; + unsigned int i; + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + char **exp_sort; + struct stream_struct *stream_sort; + bool ret = false; + + finfo.generic.level = RAW_FILEINFO_STREAM_INFORMATION; + finfo.generic.in.file.handle = h; + + status = smb2_getinfo_file(tree, tctx, &finfo); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "(%s) smb_raw_pathinfo failed: %s\n", + __location__, nt_errstr(status)); + goto fail; + } + + if (finfo.stream_info.out.num_streams != num_exp) { + torture_comment(tctx, "(%s) expected %d streams, got %d\n", + __location__, num_exp, finfo.stream_info.out.num_streams); + goto fail; + } + + if (num_exp == 0) { + ret = true; + goto fail; + } + + exp_sort = talloc_memdup(tmp_ctx, exp, num_exp * sizeof(*exp)); + + if (exp_sort == NULL) { + goto fail; + } + + TYPESAFE_QSORT(exp_sort, num_exp, qsort_string); + + stream_sort = talloc_memdup(tmp_ctx, finfo.stream_info.out.streams, + finfo.stream_info.out.num_streams * + sizeof(*stream_sort)); + + if (stream_sort == NULL) { + goto fail; + } + + TYPESAFE_QSORT(stream_sort, finfo.stream_info.out.num_streams, qsort_stream); + + for (i=0; i<num_exp; i++) { + if (strcmp(exp_sort[i], stream_sort[i].stream_name.s) != 0) { + torture_comment(tctx, + "(%s) expected stream name %s, got %s\n", + __location__, exp_sort[i], + stream_sort[i].stream_name.s); + goto fail; + } + } + + ret = true; + fail: + talloc_free(tmp_ctx); + return ret; +} + + +static bool test_stream_dir(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream.txt"; + const char *sname1; + bool ret = true; + const char *basedir_data; + struct smb2_handle h; + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + basedir_data = talloc_asprintf(mem_ctx, "%s::$DATA", DNAME); + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + torture_comment(tctx, "%s\n", sname1); + + torture_comment(tctx, "(%s) opening non-existent directory stream\n", + __location__); + ZERO_STRUCT(io.smb2); + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.desired_access = SEC_FILE_WRITE_DATA; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = sname1; + io.smb2.in.create_flags = 0; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_NOT_A_DIRECTORY); + + torture_comment(tctx, "(%s) opening basedir stream\n", __location__); + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = basedir_data; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_NOT_A_DIRECTORY); + + torture_comment(tctx, "(%s) opening basedir ::$DATA stream\n", + __location__); + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0x10; + io.smb2.in.desired_access = SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = 0; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = basedir_data; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_FILE_IS_A_DIRECTORY); + + torture_comment(tctx, "(%s) list the streams on the basedir\n", + __location__); + ret &= check_stream_list(tree, mem_ctx, DNAME, 0, NULL, h); +done: + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_stream_io(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream.txt"; + const char *sname1, *sname2; + bool ret = true; + struct smb2_handle h, h2; + + const char *one[] = { "::$DATA" }; + const char *two[] = { "::$DATA", ":Second Stream:$DATA" }; + const char *three[] = { "::$DATA", ":Stream One:$DATA", + ":Second Stream:$DATA" }; + + ZERO_STRUCT(h); + ZERO_STRUCT(h2); + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, + "Second Stream"); + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "(%s) creating a stream on a non-existent file\n", + __location__); + + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = sname1; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Stream One", NULL); + + torture_comment(tctx, "(%s) check that open of base file is allowed\n", __location__); + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.fname = fname; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, io.smb2.out.file.handle); + + torture_comment(tctx, "(%s) writing to stream\n", __location__); + status = smb2_util_write(tree, h2, "test data", 0, 9); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h2); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Stream One", "test data"); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.fname = sname1; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + + torture_comment(tctx, "(%s) modifying stream\n", __location__); + status = smb2_util_write(tree, h2, "MORE DATA ", 5, 10); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h2); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Stream One:$FOO", NULL); + + torture_comment(tctx, "(%s) creating a stream2 on a existing file\n", + __location__); + io.smb2.in.fname = sname2; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + + torture_comment(tctx, "(%s) modifying stream\n", __location__); + status= smb2_util_write(tree, h2, "SECOND STREAM", 0, 13); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, h2); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Stream One", "test MORE DATA "); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Stream One:$DATA", "test MORE DATA "); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Stream One:", NULL); + + if (!ret) { + torture_result(tctx, TORTURE_FAIL, + "check_stream(\"Stream One:*\") failed\n"); + goto done; + } + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Second Stream", "SECOND STREAM"); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "SECOND STREAM:$DATA", "SECOND STREAM"); + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Second Stream:$DATA", "SECOND STREAM"); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Second Stream:", NULL); + + ret &= check_stream(tctx, tree, __location__, mem_ctx, fname, + "Second Stream:$FOO", NULL); + + if (!ret) { + torture_result(tctx, TORTURE_FAIL, + "check_stream(\"Second Stream:*\") failed\n"); + goto done; + } + + io.smb2.in.fname = sname2; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + check_stream_list(tree, tctx, fname, 3, three, h2); + + smb2_util_close(tree, h2); + + torture_comment(tctx, "(%s) deleting stream\n", __location__); + status = smb2_util_unlink(tree, sname1); + CHECK_STATUS(status, NT_STATUS_OK); + + io.smb2.in.fname = sname2; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + check_stream_list(tree, tctx, fname, 2, two, h2); + smb2_util_close(tree, h2); + + torture_comment(tctx, "(%s) delete a stream via delete-on-close\n", + __location__); + io.smb2.in.fname = sname2; + io.smb2.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + + smb2_util_close(tree, h2); + status = smb2_util_unlink(tree, sname2); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + io.smb2.in.fname = fname; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + h2 = io.smb2.out.file.handle; + check_stream_list(tree,tctx, fname, 1, one, h2); + smb2_util_close(tree, h2); + + if (!torture_setting_bool(tctx, "samba4", false)) { + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.fname = sname1; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, io.ntcreatex.out.file.handle); + io.smb2.in.fname = sname2; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, io.ntcreatex.out.file.handle); + torture_comment(tctx, "(%s) deleting file\n", __location__); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + } + + +done: + smb2_util_close(tree, h2); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_zero_byte_stream(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream.txt"; + const char *sname; + bool ret = true; + struct smb2_handle h, bh; + const char *streams[] = { "::$DATA", ":foo:$DATA" }; + + sname = talloc_asprintf(mem_ctx, "%s:%s", fname, "foo"); + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "testdir"); + smb2_util_close(tree, h); + + torture_comment(tctx, "(%s) Check 0 byte named stream\n", + __location__); + + /* Create basefile */ + ZERO_STRUCT(io); + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.fname = fname; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "create"); + smb2_util_close(tree, io.smb2.out.file.handle); + + /* Create named stream and close it */ + ZERO_STRUCT(io); + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.fname = sname; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "create"); + smb2_util_close(tree, io.smb2.out.file.handle); + + /* + * Check stream list, the 0 byte stream MUST be returned by + * the server. + */ + ZERO_STRUCT(io); + io.smb2.in.fname = fname; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + bh = io.smb2.out.file.handle; + + ret = check_stream_list(tree,tctx, fname, 2, streams, bh); + torture_assert_goto(tctx, ret == true, ret, done, "smb2_create"); + smb2_util_close(tree, bh); + +done: + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + test stream sharemodes +*/ +static bool test_stream_sharemodes(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream_share.txt"; + const char *sname1, *sname2; + bool ret = true; + struct smb2_handle h, h1, h2; + + ZERO_STRUCT(h); + ZERO_STRUCT(h1); + ZERO_STRUCT(h2); + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, + "Second Stream"); + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "(%s) Testing stream share mode conflicts\n", + __location__); + ZERO_STRUCT(io.smb2); + io.generic.level = RAW_OPEN_SMB2; + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = sname1; + + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* + * A different stream does not give a sharing violation + */ + + io.smb2.in.fname = sname2; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + + /* + * ... whereas the same stream does with unchanged access/share_access + * flags + */ + + io.smb2.in.fname = sname1; + io.smb2.in.create_disposition = 0; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + io.smb2.in.fname = sname2; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + +done: + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + status = smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + * Test FILE_SHARE_DELETE on streams + * + * A stream opened with !FILE_SHARE_DELETE prevents the main file to be opened + * with SEC_STD_DELETE. + * + * The main file opened with !FILE_SHARE_DELETE does *not* prevent a stream to + * be opened with SEC_STD_DELETE. + * + * A stream held open with FILE_SHARE_DELETE allows the file to be + * deleted. After the main file is deleted, access to the open file descriptor + * still works, but all name-based access to both the main file as well as the + * stream is denied with DELETE pending. + * + * This means, an open of the main file with SEC_STD_DELETE should walk all + * streams and also open them with SEC_STD_DELETE. If any of these opens gives + * SHARING_VIOLATION, the main open fails. + * + * Closing the main file after delete_on_close has been set does not really + * unlink it but leaves the corresponding share mode entry with + * delete_on_close being set around until all streams are closed. + * + * Opening a stream must also look at the main file's share mode entry, look + * at the delete_on_close bit and potentially return DELETE_PENDING. + */ + +static bool test_stream_delete(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream_delete.txt"; + const char *sname1; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h1 = {{0}}; + struct smb2_read r; + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "Skipping test as samba4 is enabled\n"); + goto done; + } + + ZERO_STRUCT(h); + ZERO_STRUCT(h1); + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + + /* clean slate .. */ + smb2_util_unlink(tree, fname); + smb2_deltree(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "(%s) opening non-existent file stream\n", + __location__); + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = sname1; + + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + status = smb2_util_write(tree, h1, "test data", 0, 9); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * One stream opened without FILE_SHARE_DELETE prevents the main file + * to be deleted or even opened with DELETE access + */ + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.fname = fname; + io.smb2.in.desired_access = SEC_STD_DELETE; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + smb2_util_close(tree, h1); + + /* + * ... but unlink works if a stream is opened with FILE_SHARE_DELETE + */ + + io.smb2.in.fname = sname1; + io.smb2.in.desired_access = SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * file access still works on the stream while the main file is closed + */ + ZERO_STRUCT(r); + r.in.file.handle = h1; + r.in.length = 9; + r.in.offset = 0; + + status = smb2_read(tree, tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * name-based access to both the main file and the stream does not + * work anymore but gives DELETE_PENDING + */ + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.fname = fname; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_DELETE_PENDING); + + /* + * older S3 doesn't do this + */ + + io.smb2.in.fname = sname1; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_DELETE_PENDING); + + smb2_util_close(tree, h1); + ZERO_STRUCT(h1); + + /* + * After closing the stream the file is really gone. + */ + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.fname = fname; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + test stream names +*/ +static bool test_stream_names(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + union smb_fileinfo finfo; + union smb_fileinfo stinfo; + union smb_setfileinfo sinfo; + const char *fname = DNAME "\\stream_names.txt"; + const char *sname1, *sname1b, *sname1c, *sname1d; + const char *sname2, *snamew, *snamew2; + const char *snamer1; + bool ret = true; + struct smb2_handle h, h1, h2, h3; + int i; + const char *four[4] = { + "::$DATA", + ":\x05Stream\n One:$DATA", + ":MStream Two:$DATA", + ":?Stream*:$DATA" + }; + const char *five1[5] = { + "::$DATA", + ":\x05Stream\n One:$DATA", + ":BeforeRename:$DATA", + ":MStream Two:$DATA", + ":?Stream*:$DATA" + }; + const char *five2[5] = { + "::$DATA", + ":\x05Stream\n One:$DATA", + ":AfterRename:$DATA", + ":MStream Two:$DATA", + ":?Stream*:$DATA" + }; + + ZERO_STRUCT(h); + ZERO_STRUCT(h1); + ZERO_STRUCT(h2); + ZERO_STRUCT(h3); + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "\x05Stream\n One"); + sname1b = talloc_asprintf(mem_ctx, "%s:", sname1); + sname1c = talloc_asprintf(mem_ctx, "%s:$FOO", sname1); + sname1d = talloc_asprintf(mem_ctx, "%s:?D*a", sname1); + sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, "MStream Two"); + snamew = talloc_asprintf(mem_ctx, "%s:%s:$DATA", fname, "?Stream*"); + snamew2 = talloc_asprintf(mem_ctx, "%s\\stream*:%s:$DATA", DNAME, + "?Stream*"); + snamer1 = talloc_asprintf(mem_ctx, "%s:%s:$DATA", fname, + "BeforeRename"); + + /* clean slate ...*/ + smb2_util_unlink(tree, fname); + smb2_deltree(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "(%s) testing stream names\n", __location__); + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = sname1; + + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* + * A different stream does not give a sharing violation + */ + + io.smb2.in.fname = sname2; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.smb2.out.file.handle; + + /* + * ... whereas the same stream does with unchanged access/share_access + * flags + */ + + io.smb2.in.fname = sname1; + io.smb2.in.create_disposition = NTCREATEX_DISP_SUPERSEDE; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + io.smb2.in.fname = sname1b; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + + io.smb2.in.fname = sname1c; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + /* w2k returns INVALID_PARAMETER */ + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + } else { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + } + + io.smb2.in.fname = sname1d; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { + /* w2k returns INVALID_PARAMETER */ + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + } else { + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + } + + io.smb2.in.fname = sname2; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + io.smb2.in.fname = snamew; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.smb2.out.file.handle; + + io.smb2.in.fname = snamew2; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_INVALID); + + io.smb2.in.fname = fname; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + ret &= check_stream_list(tree, tctx, fname, 4, four, + io.smb2.out.file.handle); + CHECK_VALUE(ret, true); + smb2_util_close(tree, h1); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + if (torture_setting_bool(tctx, "samba4", true)) { + goto done; + } + + finfo.generic.level = RAW_FILEINFO_ALL_INFORMATION; + finfo.generic.in.file.handle = io.smb2.out.file.handle; + status = smb2_getinfo_file(tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + ret &= check_stream_list(tree, tctx, fname, 4, four, + io.smb2.out.file.handle); + + CHECK_VALUE(ret, true); + for (i=0; i < 4; i++) { + NTTIME write_time; + uint64_t stream_size; + char *path = talloc_asprintf(tctx, "%s%s", + fname, four[i]); + + char *rpath = talloc_strdup(path, path); + char *p = strrchr(rpath, ':'); + /* eat :$DATA */ + *p = 0; + p--; + if (*p == ':') { + /* eat ::$DATA */ + *p = 0; + } + torture_comment(tctx, "(%s): i[%u][%s]\n", + __location__, i,path); + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_RIGHTS_FILE_ALL; + io.smb2.in.fname = path; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + finfo.generic.level = RAW_FILEINFO_ALL_INFORMATION; + finfo.generic.in.file.path = fname; + status = smb2_getinfo_file(tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + + stinfo.generic.level = RAW_FILEINFO_ALL_INFORMATION; + stinfo.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree, mem_ctx, &stinfo); + CHECK_STATUS(status, NT_STATUS_OK); + if (!torture_setting_bool(tctx, "samba3", false)) { + CHECK_NTTIME(stinfo.all_info.out.create_time, + finfo.all_info.out.create_time); + CHECK_NTTIME(stinfo.all_info.out.access_time, + finfo.all_info.out.access_time); + CHECK_NTTIME(stinfo.all_info.out.write_time, + finfo.all_info.out.write_time); + CHECK_NTTIME(stinfo.all_info.out.change_time, + finfo.all_info.out.change_time); + } + CHECK_VALUE(stinfo.all_info.out.attrib, + finfo.all_info.out.attrib); + CHECK_VALUE(stinfo.all_info.out.size, + finfo.all_info.out.size); + CHECK_VALUE(stinfo.all_info.out.delete_pending, + finfo.all_info.out.delete_pending); + CHECK_VALUE(stinfo.all_info.out.directory, + finfo.all_info.out.directory); + CHECK_VALUE(stinfo.all_info.out.ea_size, + finfo.all_info.out.ea_size); + + stinfo.generic.level = RAW_FILEINFO_NAME_INFORMATION; + stinfo.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree, mem_ctx, &stinfo); + CHECK_STATUS(status, NT_STATUS_OK); + if (!torture_setting_bool(tctx, "samba3", false)) { + CHECK_STR(rpath, stinfo.name_info.out.fname.s); + } + + write_time = finfo.all_info.out.write_time; + write_time += i*1000000; + write_time /= 1000000; + write_time *= 1000000; + + ZERO_STRUCT(sinfo); + sinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION; + sinfo.basic_info.in.file.handle = h1; + sinfo.basic_info.in.write_time = write_time; + sinfo.basic_info.in.attrib = stinfo.all_info.out.attrib; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + stream_size = i*8192; + + ZERO_STRUCT(sinfo); + sinfo.end_of_file_info.level = + RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sinfo.end_of_file_info.in.file.handle = h1; + sinfo.end_of_file_info.in.size = stream_size; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + stinfo.generic.level = RAW_FILEINFO_ALL_INFORMATION; + stinfo.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree, mem_ctx, &stinfo); + CHECK_STATUS(status, NT_STATUS_OK); + if (!torture_setting_bool(tctx, "samba3", false)) { + CHECK_NTTIME(stinfo.all_info.out.write_time, + write_time); + CHECK_VALUE(stinfo.all_info.out.attrib, + finfo.all_info.out.attrib); + } + CHECK_VALUE(stinfo.all_info.out.size, + stream_size); + CHECK_VALUE(stinfo.all_info.out.delete_pending, + finfo.all_info.out.delete_pending); + CHECK_VALUE(stinfo.all_info.out.directory, + finfo.all_info.out.directory); + CHECK_VALUE(stinfo.all_info.out.ea_size, + finfo.all_info.out.ea_size); + + io.smb2.in.fname = fname; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + ret &= check_stream_list(tree, tctx, fname, 4, four, + io.smb2.out.file.handle); + + smb2_util_close(tree, h1); + talloc_free(path); + } + + torture_comment(tctx, "(%s): testing stream renames\n", __location__); + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_RIGHTS_FILE_ALL; + io.smb2.in.fname = snamer1; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + ret &= check_stream_list(tree,tctx, fname, 5, five1, + io.smb2.out.file.handle); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = ":AfterRename:$DATA"; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + ret &= check_stream_list(tree,tctx, fname, 5, five2, + io.smb2.out.file.handle); + + CHECK_VALUE(ret, true); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = false; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = ":MStream Two:$DATA"; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION); + + ret &= check_stream_list(tree,tctx, fname, 5, five2, + io.smb2.out.file.handle); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = ":MStream Two:$DATA"; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + ret &= check_stream_list(tree,tctx, fname, 5, five2, + io.smb2.out.file.handle); + + CHECK_VALUE(ret, true); + /* TODO: we need to test more rename combinations */ + +done: + smb2_util_close(tree, h1); + status = smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + test stream names +*/ +static bool test_stream_names2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream_names2.txt"; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h1 = {{0}}; + uint8_t i; + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "(%s) testing stream names\n", __location__); + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_WRITE_DATA; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + for (i=0x01; i < 0x7F; i++) { + char *path = talloc_asprintf(mem_ctx, "%s:Stream%c0x%02X:$DATA", + fname, i, i); + NTSTATUS expected; + + switch (i) { + case '/':/*0x2F*/ + case ':':/*0x3A*/ + case '\\':/*0x5C*/ + expected = NT_STATUS_OBJECT_NAME_INVALID; + break; + default: + expected = NT_STATUS_OBJECT_NAME_NOT_FOUND; + break; + } + + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.fname = path; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + if (!NT_STATUS_EQUAL(status, expected)) { + torture_comment(tctx, + "(%s) %s:Stream%c0x%02X:$DATA%s => expected[%s]\n", + __location__, fname, isprint(i)?(char)i:' ', i, + isprint(i)?"":" (not printable)", + nt_errstr(expected)); + } + CHECK_STATUS(status, expected); + + talloc_free(path); + } + +done: + smb2_util_close(tree, h1); + status = smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +/* + test case insensitive stream names +*/ +static bool test_stream_names3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_fsinfo info; + const char *fname = DNAME "\\stream_names3.txt"; + const char *sname = NULL; + const char *snamel = NULL; + const char *snameu = NULL; + const char *sdname = NULL; + const char *sdnamel = NULL; + const char *sdnameu = NULL; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle hf = {{0}}; + struct smb2_handle hs = {{0}}; + struct smb2_handle hsl = {{0}}; + struct smb2_handle hsu = {{0}}; + struct smb2_handle hsd = {{0}}; + struct smb2_handle hsdl = {{0}}; + struct smb2_handle hsdu = {{0}}; + const char *streams[] = { "::$DATA", ":StreamName:$DATA", }; + + smb2_deltree(tree, DNAME); + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(info); + info.generic.level = RAW_QFS_ATTRIBUTE_INFORMATION; + info.generic.handle = h; + status = smb2_getinfo_fs(tree, tree, &info); + CHECK_STATUS(status, NT_STATUS_OK); + if (!(info.attribute_info.out.fs_attr & FILE_CASE_SENSITIVE_SEARCH)) { + torture_skip(tctx, "No FILE_CASE_SENSITIVE_SEARCH supported"); + } + + /* + * We create the following file: + * + * teststreams\\stream_names3.txt + * + * and add a stream named 'StreamName' + * + * Then we try to open the stream using the following names: + * + * teststreams\\stream_names3.txt:StreamName + * teststreams\\stream_names3.txt:streamname + * teststreams\\stream_names3.txt:STREAMNAME + * teststreams\\stream_names3.txt:StreamName:$dAtA + * teststreams\\stream_names3.txt:streamname:$data + * teststreams\\stream_names3.txt:STREAMNAME:$DATA + */ + sname = talloc_asprintf(tctx, "%s:StreamName", fname); + torture_assert_not_null(tctx, sname, __location__); + snamel = strlower_talloc(tctx, sname); + torture_assert_not_null(tctx, snamel, __location__); + snameu = strupper_talloc(tctx, sname); + torture_assert_not_null(tctx, snameu, __location__); + + sdname = talloc_asprintf(tctx, "%s:$dAtA", sname); + torture_assert_not_null(tctx, sdname, __location__); + sdnamel = strlower_talloc(tctx, sdname); + torture_assert_not_null(tctx, sdnamel, __location__); + sdnameu = strupper_talloc(tctx, sdname); + torture_assert_not_null(tctx, sdnameu, __location__); + + torture_comment(tctx, "(%s) testing case insensitive stream names\n", + __location__); + status = torture_smb2_testfile(tree, fname, &hf); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_smb2_testfile(tree, sname, &hs); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, hs); + + torture_assert(tctx, + check_stream_list(tree, tctx, fname, + ARRAY_SIZE(streams), + streams, + hf), + "streams"); + + status = torture_smb2_open(tree, sname, SEC_RIGHTS_FILE_ALL, &hs); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_smb2_open(tree, snamel, SEC_RIGHTS_FILE_ALL, &hsl); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_smb2_open(tree, snameu, SEC_RIGHTS_FILE_ALL, &hsu); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_smb2_open(tree, sdname, SEC_RIGHTS_FILE_ALL, &hsd); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_smb2_open(tree, sdnamel, SEC_RIGHTS_FILE_ALL, &hsdl); + CHECK_STATUS(status, NT_STATUS_OK); + status = torture_smb2_open(tree, sdnameu, SEC_RIGHTS_FILE_ALL, &hsdu); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + smb2_util_close(tree, hsdu); + smb2_util_close(tree, hsdl); + smb2_util_close(tree, hsd); + smb2_util_close(tree, hsu); + smb2_util_close(tree, hsl); + smb2_util_close(tree, hs); + smb2_util_close(tree, hf); + smb2_util_close(tree, h); + status = smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +#define CHECK_CALL_HANDLE(call, rightstatus) do { \ + sfinfo.generic.level = RAW_SFILEINFO_ ## call; \ + sfinfo.generic.in.file.handle = h1; \ + status = smb2_setinfo_file(tree, &sfinfo); \ + if (!NT_STATUS_EQUAL(status, rightstatus)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s - %s (should be %s)\n", \ + __location__, #call, \ + nt_errstr(status), nt_errstr(rightstatus)); \ + ret = false; \ + } \ + finfo1.generic.level = RAW_FILEINFO_ALL_INFORMATION; \ + finfo1.generic.in.file.handle = h1; \ + status2 = smb2_getinfo_file(tree, tctx, &finfo1); \ + if (!NT_STATUS_IS_OK(status2)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) %s pathinfo - %s\n", \ + __location__, #call, nt_errstr(status)); \ + ret = false; \ + }} while (0) + +/* + test stream renames +*/ +static bool test_stream_rename(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status, status2; + union smb_open io; + const char *fname = DNAME "\\stream_rename.txt"; + const char *sname1, *sname2; + union smb_fileinfo finfo1; + union smb_setfileinfo sfinfo; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h1 = {{0}}; + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname, "Stream One"); + sname2 = talloc_asprintf(mem_ctx, "%s:%s:$DaTa", fname, + "Second Stream"); + + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(tctx, "(%s) testing stream renames\n", __location__); + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_RIGHTS_FILE_ALL; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = sname1; + + /* Create two streams. */ + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + smb2_util_close(tree, h1); + + io.smb2.in.fname = sname2; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + smb2_util_close(tree, h1); + + /* + * Open the second stream. + */ + + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* + * Now rename the second stream onto the first. + */ + + ZERO_STRUCT(sfinfo); + + sfinfo.rename_information.in.overwrite = 1; + sfinfo.rename_information.in.root_fid = 0; + sfinfo.rename_information.in.new_name = ":Stream One"; + CHECK_CALL_HANDLE(RENAME_INFORMATION, NT_STATUS_OK); +done: + smb2_util_close(tree, h1); + status = smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +static bool test_stream_rename2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname1 = DNAME "\\stream_rename2.txt"; + const char *fname2 = DNAME "\\stream2_rename2.txt"; + const char *stream_name1 = ":Stream One:$DATA"; + const char *stream_name2 = ":Stream Two:$DATA"; + const char *stream_name_default = "::$DATA"; + const char *sname1; + const char *sname2; + bool ret = true; + struct smb2_handle h, h1; + union smb_setfileinfo sinfo; + + ZERO_STRUCT(h); + ZERO_STRUCT(h1); + + sname1 = talloc_asprintf(mem_ctx, "%s:%s", fname1, "Stream One"); + sname2 = talloc_asprintf(mem_ctx, "%s:%s", fname1, "Stream Two"); + + smb2_util_unlink(tree, fname1); + smb2_util_unlink(tree, fname2); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA | + SEC_STD_DELETE | + SEC_FILE_APPEND_DATA | + SEC_STD_READ_CONTROL; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = sname1; + + /* Open/create new stream. */ + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, io.smb2.out.file.handle); + + /* + * Reopen the stream for SMB2 renames. + */ + io.smb2.in.fname = sname1; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* + * Check SMB2 rename of a stream using :<stream>. + */ + torture_comment(tctx, "(%s) Checking SMB2 rename of a stream using " + ":<stream>\n", __location__); + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION_SMB2; + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.overwrite = 1; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = stream_name1; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * Check SMB2 rename of an overwriting stream using :<stream>. + */ + torture_comment(tctx, "(%s) Checking SMB2 rename of an overwriting " + "stream using :<stream>\n", __location__); + + /* Create second stream. */ + io.smb2.in.fname = sname2; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + smb2_util_close(tree, io.smb2.out.file.handle); + + /* Rename the first stream onto the second. */ + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.new_name = stream_name2; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h1); + + /* + * Reopen the stream with the new name. + */ + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.fname = sname2; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + /* + * Check SMB2 rename of a stream using <base>:<stream>. + */ + torture_comment(tctx, "(%s) Checking SMB2 rename of a stream using " + "<base>:<stream>\n", __location__); + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.new_name = sname1; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION); + + if (!torture_setting_bool(tctx, "samba4", false)) { + /* + * Check SMB2 rename to the default stream using :<stream>. + */ + torture_comment(tctx, "(%s) Checking SMB2 rename to default stream " + "using :<stream>\n", __location__); + sinfo.rename_information.in.file.handle = h1; + sinfo.rename_information.in.new_name = stream_name_default; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + } + + smb2_util_close(tree, h1); + + done: + smb2_util_close(tree, h1); + status = smb2_util_unlink(tree, fname1); + status = smb2_util_unlink(tree, fname2); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +static bool create_file_with_stream(struct torture_context *tctx, + struct smb2_tree *tree, + TALLOC_CTX *mem_ctx, + const char *base_fname, + const char *stream) +{ + NTSTATUS status; + bool ret = true; + union smb_open io; + + /* Create a file with a stream */ + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_STD_READ_CONTROL; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = stream; + + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + done: + smb2_util_close(tree, io.smb2.out.file.handle); + return ret; +} + + +/* Test how streams interact with create dispositions */ +static bool test_stream_create_disposition(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream_create_disp.txt"; + const char *stream = "Stream One:$DATA"; + const char *fname_stream; + const char *default_stream_name = "::$DATA"; + const char *stream_list[2]; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h1 = {{0}}; + + /* clean slate .. */ + smb2_util_unlink(tree, fname); + smb2_deltree(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + fname_stream = talloc_asprintf(mem_ctx, "%s:%s", fname, stream); + + stream_list[0] = talloc_asprintf(mem_ctx, ":%s", stream); + stream_list[1] = default_stream_name; + + if (!create_file_with_stream(tctx, tree, mem_ctx, fname, + fname_stream)) { + goto done; + } + + /* Open the base file with OPEN */ + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_STD_READ_CONTROL; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + /* + * check create open: sanity check + */ + torture_comment(tctx, "(%s) Checking create disp: open\n", + __location__); + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + if (!check_stream_list(tree, tctx, fname, 2, stream_list, + io.smb2.out.file.handle)) { + goto done; + } + smb2_util_close(tree, io.smb2.out.file.handle); + + /* + * check create overwrite + */ + torture_comment(tctx, "(%s) Checking create disp: overwrite\n", + __location__); + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + if (!check_stream_list(tree, tctx, fname, 1, &default_stream_name, + io.smb2.out.file.handle)) { + goto done; + } + smb2_util_close(tree, io.smb2.out.file.handle); + + /* + * check create overwrite_if + */ + torture_comment(tctx, "(%s) Checking create disp: overwrite_if\n", + __location__); + smb2_util_unlink(tree, fname); + if (!create_file_with_stream(tctx, tree, mem_ctx, fname, fname_stream)) + goto done; + + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + if (!check_stream_list(tree, tctx, fname, 1, &default_stream_name, + io.smb2.out.file.handle)) { + goto done; + } + smb2_util_close(tree, io.smb2.out.file.handle); + + /* + * check create supersede + */ + torture_comment(tctx, "(%s) Checking create disp: supersede\n", + __location__); + smb2_util_unlink(tree, fname); + if (!create_file_with_stream(tctx, tree, mem_ctx, fname, + fname_stream)) { + goto done; + } + + io.smb2.in.create_disposition = NTCREATEX_DISP_SUPERSEDE; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + if (!check_stream_list(tree, tctx, fname, 1, &default_stream_name, + io.smb2.out.file.handle)) { + goto done; + } + smb2_util_close(tree, io.smb2.out.file.handle); + + /* + * check create overwrite_if on a stream. + */ + torture_comment(tctx, "(%s) Checking create disp: overwrite_if on " + "stream\n", __location__); + smb2_util_unlink(tree, fname); + if (!create_file_with_stream(tctx, tree, mem_ctx, fname, + fname_stream)) { + goto done; + } + + io.smb2.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.smb2.in.fname = fname_stream; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + if (!check_stream_list(tree, tctx, fname, 2, stream_list, + io.smb2.out.file.handle)) { + goto done; + } + smb2_util_close(tree, io.smb2.out.file.handle); + done: + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +static bool open_stream(struct smb2_tree *tree, + struct torture_context *mem_ctx, + const char *fname, + struct smb2_handle *h_out) +{ + NTSTATUS status; + union smb_open io; + + ZERO_STRUCT(io.smb2); + io.smb2.in.create_flags = 0; + io.smb2.in.desired_access = SEC_FILE_READ_DATA | + SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_STD_READ_CONTROL | + SEC_FILE_WRITE_ATTRIBUTE; + io.smb2.in.create_options = 0; + io.smb2.in.file_attributes = 0; + io.smb2.in.share_access = 0; + io.smb2.in.alloc_size = 0; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io.smb2.in.security_flags = 0; + io.smb2.in.fname = fname; + + status = smb2_create(tree, mem_ctx, &(io.smb2)); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + *h_out = io.smb2.out.file.handle; + return true; +} + + +/* Test the effect of setting attributes on a stream. */ +static bool test_stream_attributes1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + bool ret = true; + NTSTATUS status; + union smb_open io; + const char *fname = DNAME "\\stream_attr.txt"; + const char *stream = "Stream One:$DATA"; + const char *fname_stream; + struct smb2_handle h, h1; + union smb_fileinfo finfo; + union smb_setfileinfo sfinfo; + time_t basetime = (time(NULL) - 86400) & ~1; + + ZERO_STRUCT(h); + ZERO_STRUCT(h1); + + torture_comment(tctx, "(%s) testing attribute setting on stream\n", + __location__); + + /* clean slate .. */ + smb2_util_unlink(tree, fname); + smb2_deltree(tree, fname); + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h); + CHECK_STATUS(status, NT_STATUS_OK); + + fname_stream = talloc_asprintf(mem_ctx, "%s:%s", fname, stream); + + /* Create a file with a stream with attribute FILE_ATTRIBUTE_ARCHIVE. */ + ret = create_file_with_stream(tctx, tree, mem_ctx, fname, + fname_stream); + if (!ret) { + goto done; + } + + ZERO_STRUCT(io.smb2); + io.smb2.in.fname = fname; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(finfo); + finfo.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + finfo.generic.in.file.handle = io.smb2.out.file.handle; + status = smb2_getinfo_file(tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_OK); + + if (finfo.basic_info.out.attrib != FILE_ATTRIBUTE_ARCHIVE) { + torture_comment(tctx, "(%s) Incorrect attrib %x - should be " + "%x\n", __location__, + (unsigned int)finfo.basic_info.out.attrib, + (unsigned int)FILE_ATTRIBUTE_ARCHIVE); + ret = false; + goto done; + } + + smb2_util_close(tree, io.smb2.out.file.handle); + /* Now open the stream name. */ + + if (!open_stream(tree, tctx, fname_stream, &h1)) { + goto done; + } + + /* Change the time on the stream. */ + ZERO_STRUCT(sfinfo); + unix_to_nt_time(&sfinfo.basic_info.in.write_time, basetime); + sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sfinfo.generic.in.file.handle = h1; + status = smb2_setinfo_file(tree, &sfinfo); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(tctx, "(%s) %s - %s (should be %s)\n", + __location__, "SETATTR", + nt_errstr(status), nt_errstr(NT_STATUS_OK)); + ret = false; + goto done; + } + + smb2_util_close(tree, h1); + + ZERO_STRUCT(io.smb2); + io.smb2.in.fname = fname; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + ZERO_STRUCT(finfo); + finfo.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + finfo.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree, mem_ctx, &finfo); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "(%s) %s pathinfo - %s\n", + __location__, "SETATTRE", nt_errstr(status)); + ret = false; + goto done; + } + + if (nt_time_to_unix(finfo.basic_info.out.write_time) != basetime) { + torture_comment(tctx, "(%s) time incorrect.\n", __location__); + ret = false; + goto done; + } + smb2_util_close(tree, h1); + + if (!open_stream(tree, tctx, fname_stream, &h1)) { + goto done; + } + + /* Changing attributes on stream */ + ZERO_STRUCT(sfinfo); + sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_READONLY; + + sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + sfinfo.generic.in.file.handle = h1; + status = smb2_setinfo_file(tree, &sfinfo); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + torture_comment(tctx, "(%s) %s - %s (should be %s)\n", + __location__, "SETATTR", + nt_errstr(status), nt_errstr(NT_STATUS_OK)); + ret = false; + goto done; + } + + smb2_util_close(tree, h1); + + ZERO_STRUCT(io.smb2); + io.smb2.in.fname = fname; + io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN; + io.smb2.in.desired_access = SEC_FILE_READ_DATA; + status = smb2_create(tree, mem_ctx, &(io.smb2)); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.smb2.out.file.handle; + + ZERO_STRUCT(finfo); + finfo.generic.level = RAW_FILEINFO_BASIC_INFORMATION; + finfo.generic.in.file.handle = h1; + status = smb2_getinfo_file(tree, mem_ctx, &finfo); + CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED); + +done: + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname); + smb2_deltree(tree, DNAME); + talloc_free(mem_ctx); + + return ret; +} + +static bool check_metadata(struct torture_context *tctx, + struct smb2_tree *tree, + const char *path, + struct smb2_handle _h, + NTTIME expected_btime, + uint32_t expected_attribs) +{ + struct smb2_handle h = _h; + union smb_fileinfo getinfo; + NTSTATUS status; + bool ret = true; + + if (smb2_util_handle_empty(h)) { + struct smb2_create c; + + c = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_HIDDEN, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = path, + }; + status = smb2_create(tree, tctx, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + h = c.out.file.handle; + } + + getinfo = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = h, + }; + + status = smb2_getinfo_file(tree, tctx, &getinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + torture_assert_u64_equal_goto(tctx, + expected_btime, + getinfo.basic_info.out.create_time, + ret, done, + "btime was updated\n"); + + torture_assert_u32_equal_goto(tctx, + expected_attribs, + getinfo.basic_info.out.attrib, + ret, done, + "btime was updated\n"); + +done: + if (smb2_util_handle_empty(_h)) { + smb2_util_close(tree, h); + } + + return ret; +} + +static bool test_stream_attributes2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + NTSTATUS status; + struct smb2_create c1; + struct smb2_handle h1 = {{0}}; + const char *fname = DNAME "\\test_stream_btime"; + const char *sname = DNAME "\\test_stream_btime:stream"; + union smb_fileinfo getinfo; + union smb_setfileinfo setinfo; + const char *data = "test data"; + struct timespec ts; + NTTIME btime; + uint32_t attrib = FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_ARCHIVE; + bool ret; + + smb2_deltree(tree, DNAME); + + status = torture_smb2_testdir(tree, DNAME, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree, h1); + + torture_comment(tctx, "Let's dance!\n"); + + /* + * Step 1: create file and get creation date + */ + + c1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = FILE_ATTRIBUTE_HIDDEN, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = fname, + }; + status = smb2_create(tree, tctx, &c1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c1.out.file.handle; + + getinfo = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tctx, &getinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + btime = getinfo.basic_info.out.create_time; + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + /* + * Step X: write to file, assert btime was not updated + */ + + c1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = attrib, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = fname, + }; + status = smb2_create(tree, tctx, &c1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c1.out.file.handle; + + status = smb2_util_write(tree, h1, data, 0, strlen(data)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + ret = check_metadata(tctx, tree, NULL, h1, btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + ret = check_metadata(tctx, tree, fname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + /* + * Step X: create stream, assert creation date is the same + * as the one on the basefile + */ + + c1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = attrib, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = sname, + }; + status = smb2_create(tree, tctx, &c1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c1.out.file.handle; + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + ret = check_metadata(tctx, tree, sname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + /* + * Step X: set btime on stream, verify basefile has the same btime. + */ + + c1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = attrib, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = sname, + }; + status = smb2_create(tree, tctx, &c1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c1.out.file.handle; + + setinfo = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = h1, + }; + clock_gettime_mono(&ts); + btime = setinfo.basic_info.in.create_time = full_timespec_to_nt_time(&ts); + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + ret = check_metadata(tctx, tree, NULL, h1, btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad time on stream\n"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + ret = check_metadata(tctx, tree, fname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad time on basefile\n"); + + ret = check_metadata(tctx, tree, sname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad time on stream\n"); + + /* + * Step X: write to stream, assert btime was not updated + */ + + c1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = attrib, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = sname, + }; + status = smb2_create(tree, tctx, &c1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c1.out.file.handle; + + status = smb2_util_write(tree, h1, data, 0, strlen(data)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + ret = check_metadata(tctx, tree, NULL, h1, btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + ret = check_metadata(tctx, tree, fname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + ret = check_metadata(tctx, tree, sname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + /* + * Step X: modify attributes via stream, verify it's "also" set on the + * basefile. + */ + + c1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = attrib, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = sname, + }; + status = smb2_create(tree, tctx, &c1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c1.out.file.handle; + + attrib = FILE_ATTRIBUTE_NORMAL; + + setinfo = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = h1, + .basic_info.in.attrib = attrib, + }; + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + ret = check_metadata(tctx, tree, fname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + ret = check_metadata(tctx, tree, sname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + /* + * Step X: modify attributes via basefile, verify it's "also" set on the + * stream. + */ + + c1 = (struct smb2_create) { + .in.desired_access = SEC_FILE_ALL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.file_attributes = attrib, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION, + .in.fname = fname, + }; + status = smb2_create(tree, tctx, &c1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c1.out.file.handle; + + attrib = FILE_ATTRIBUTE_HIDDEN; + + setinfo = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = h1, + .basic_info.in.attrib = attrib, + }; + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + ret = check_metadata(tctx, tree, fname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + + ret = check_metadata(tctx, tree, sname, (struct smb2_handle){{0}}, + btime, attrib); + torture_assert_goto(tctx, ret, ret, done, "Bad metadata\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + + smb2_deltree(tree, DNAME); + + return ret; +} + +static bool test_basefile_rename_with_open_stream(struct torture_context *tctx, + struct smb2_tree *tree) +{ + bool ret = true; + NTSTATUS status; + struct smb2_tree *tree2 = NULL; + struct smb2_create create, create2; + struct smb2_handle h1 = {{0}}, h2 = {{0}}; + const char *fname = "test_rename_openfile"; + const char *sname = "test_rename_openfile:foo"; + const char *fname_renamed = "test_rename_openfile_renamed"; + union smb_setfileinfo sinfo; + const char *data = "test data"; + + ret = torture_smb2_connection(tctx, &tree2); + torture_assert_goto(tctx, ret == true, ret, done, + "torture_smb2_connection failed\n"); + + torture_comment(tctx, "Creating file with stream\n"); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_FILE_ALL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + create.in.fname = sname; + + status = smb2_create(tree, tctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + h1 = create.out.file.handle; + + torture_comment(tctx, "Writing to stream\n"); + + status = smb2_util_write(tree, h1, data, 0, strlen(data)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + torture_comment(tctx, "Renaming base file\n"); + + ZERO_STRUCT(create2); + create2.in.desired_access = SEC_FILE_ALL; + create2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create2.in.share_access = NTCREATEX_SHARE_ACCESS_MASK; + create2.in.create_disposition = NTCREATEX_DISP_OPEN; + create2.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + create2.in.fname = fname; + + status = smb2_create(tree2, tctx, &create2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + h2 = create2.out.file.handle; + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h2; + sinfo.rename_information.in.new_name = fname_renamed; + + status = smb2_setinfo_file(tree2, &sinfo); + torture_assert_ntstatus_equal_goto( + tctx, status, NT_STATUS_ACCESS_DENIED, ret, done, + "smb2_setinfo_file didn't return NT_STATUS_ACCESS_DENIED\n"); + + smb2_util_close(tree2, h2); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree2, h2); + } + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname_renamed); + + return ret; +} + +/* + basic testing of streams calls SMB2 +*/ +struct torture_suite *torture_smb2_streams_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = + torture_suite_create(ctx, "streams"); + + torture_suite_add_1smb2_test(suite, "dir", test_stream_dir); + torture_suite_add_1smb2_test(suite, "io", test_stream_io); + torture_suite_add_1smb2_test(suite, "sharemodes", test_stream_sharemodes); + torture_suite_add_1smb2_test(suite, "names", test_stream_names); + torture_suite_add_1smb2_test(suite, "names2", test_stream_names2); + torture_suite_add_1smb2_test(suite, "names3", test_stream_names3); + torture_suite_add_1smb2_test(suite, "rename", test_stream_rename); + torture_suite_add_1smb2_test(suite, "rename2", test_stream_rename2); + torture_suite_add_1smb2_test(suite, "create-disposition", test_stream_create_disposition); + torture_suite_add_1smb2_test(suite, "attributes1", test_stream_attributes1); + torture_suite_add_1smb2_test(suite, "attributes2", test_stream_attributes2); + torture_suite_add_1smb2_test(suite, "delete", test_stream_delete); + torture_suite_add_1smb2_test(suite, "zero-byte", test_zero_byte_stream); + torture_suite_add_1smb2_test(suite, "basefile-rename-with-open-stream", + test_basefile_rename_with_open_stream); + + suite->description = talloc_strdup(suite, "SMB2-STREAM tests"); + return suite; +} diff --git a/source4/torture/smb2/tcon.c b/source4/torture/smb2/tcon.c new file mode 100644 index 0000000..1e658af --- /dev/null +++ b/source4/torture/smb2/tcon.c @@ -0,0 +1,146 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester + Copyright (C) Andrew Tridgell 1997-2003 + Copyright (C) Jelmer Vernooij 2006 + Copyright (C) David Mulder 2020 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/smbtorture.h" +#include "torture/smb2/proto.h" +#include "libcli/smb/smbXcli_base.h" +#include "torture/util.h" +#include "system/filesys.h" +#include "system/time.h" +#include "libcli/resolve/resolve.h" +#include "lib/events/events.h" +#include "param/param.h" + +static void smb2cli_session_set_id(struct smbXcli_session *session, + uint64_t session_id) +{ + smb2cli_session_set_id_and_flags(session, session_id, + smb2cli_session_get_flags(session)); +} + +/** + this checks to see if a secondary tconx can use open files from an + earlier tconx + */ +bool run_tcon_test(struct torture_context *tctx, struct smb2_tree *tree) +{ + const char *fname = "tcontest.tmp"; + struct smb2_handle fnum1; + uint32_t cnum1, cnum2, cnum3; + uint64_t sessid1, sessid2; + uint8_t buf[4]; + bool ret = true; + struct smb2_tree *tree1 = NULL; + const char *host = torture_setting_string(tctx, "host", NULL); + struct smb2_create io = {0}; + NTSTATUS status; + bool ok; + + if (smb2_deltree(tree, fname) == -1) { + torture_comment(tctx, "unlink of %s failed\n", fname); + } + + io.in.fname = fname; + io.in.desired_access = SEC_FILE_READ_DATA | SEC_FILE_WRITE_DATA; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree, tree, &io); + if (NT_STATUS_IS_ERR(status)) { + torture_result(tctx, TORTURE_FAIL, "open of %s failed (%s)\n", fname, nt_errstr(status)); + return false; + } + fnum1 = io.out.file.handle; + + cnum1 = smb2cli_tcon_current_id(tree->smbXcli); + sessid1 = smb2cli_session_current_id(tree->session->smbXcli); + + memset(buf, 0, 4); /* init buf so valgrind won't complain */ + status = smb2_util_write(tree, fnum1, buf, 130, 4); + if (NT_STATUS_IS_ERR(status)) { + torture_result(tctx, TORTURE_FAIL, "initial write failed (%s)\n", nt_errstr(status)); + return false; + } + + ok = torture_smb2_tree_connect(tctx, tree->session, tctx, &tree1); + if (!ok) { + torture_result(tctx, TORTURE_FAIL, "%s refused 2nd tree connect\n", host); + return false; + } + + cnum2 = smb2cli_tcon_current_id(tree1->smbXcli); + cnum3 = MAX(cnum1, cnum2) + 1; /* any invalid number */ + sessid2 = smb2cli_session_current_id(tree1->session->smbXcli) + 1; + + /* try a write with the wrong tid */ + smb2cli_tcon_set_id(tree1->smbXcli, cnum2); + + status = smb2_util_write(tree1, fnum1, buf, 130, 4); + if (NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "* server allows write with wrong TID\n"); + ret = false; + } else { + torture_comment(tctx, "server fails write with wrong TID : %s\n", nt_errstr(status)); + } + + + /* try a write with an invalid tid */ + smb2cli_tcon_set_id(tree1->smbXcli, cnum3); + + status = smb2_util_write(tree1, fnum1, buf, 130, 4); + if (NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "* server allows write with invalid TID\n"); + ret = false; + } else { + torture_comment(tctx, "server fails write with invalid TID : %s\n", nt_errstr(status)); + } + + /* try a write with an invalid session id */ + smb2cli_session_set_id(tree1->session->smbXcli, sessid2); + smb2cli_tcon_set_id(tree1->smbXcli, cnum1); + + status = smb2_util_write(tree1, fnum1, buf, 130, 4); + if (NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "* server allows write with invalid VUID\n"); + ret = false; + } else { + torture_comment(tctx, "server fails write with invalid VUID : %s\n", nt_errstr(status)); + } + + smb2cli_session_set_id(tree1->session->smbXcli, sessid1); + smb2cli_tcon_set_id(tree1->smbXcli, cnum1); + + status = smb2_util_close(tree1, fnum1); + if (NT_STATUS_IS_ERR(status)) { + torture_result(tctx, TORTURE_FAIL, "close failed (%s)\n", nt_errstr(status)); + return false; + } + + smb2cli_tcon_set_id(tree1->smbXcli, cnum2); + + smb2_util_unlink(tree1, fname); + + return ret; +} diff --git a/source4/torture/smb2/timestamps.c b/source4/torture/smb2/timestamps.c new file mode 100644 index 0000000..0e3f0c0 --- /dev/null +++ b/source4/torture/smb2/timestamps.c @@ -0,0 +1,1349 @@ +/* + Unix SMB/CIFS implementation. + + test timestamps + + Copyright (C) Ralph Boehme 2019 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/util.h" +#include "torture/smb2/proto.h" + +#define BASEDIR "smb2-timestamps" +#define FNAME "testfile.dat" + +static bool test_close_no_attrib(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *filename = BASEDIR "/" FNAME; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + struct smb2_handle testdirh = {{0}}; + struct smb2_close c; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + status = torture_smb2_testdir(tree, BASEDIR, &testdirh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree, testdirh); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = filename, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + c = (struct smb2_close) { + .in.file.handle = handle, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(handle); + + torture_assert_u64_equal_goto(tctx, c.out.create_time, NTTIME_OMIT, + ret, done, "Unexpected create time\n"); + torture_assert_u64_equal_goto(tctx, c.out.access_time, NTTIME_OMIT, + ret, done, "Unexpected access time\n"); + torture_assert_u64_equal_goto(tctx, c.out.write_time, NTTIME_OMIT, + ret, done, "Unexpected write time\n"); + torture_assert_u64_equal_goto(tctx, c.out.change_time, NTTIME_OMIT, + ret, done, "Unexpected change time\n"); + torture_assert_u64_equal_goto(tctx, c.out.alloc_size, 0, + ret, done, "Unexpected allocation size\n"); + torture_assert_u64_equal_goto(tctx, c.out.size, 0, + ret, done, "Unexpected size\n"); + torture_assert_u64_equal_goto(tctx, c.out.file_attr, 0, + ret, done, "Unexpected attributes\n"); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_time_t(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + time_t t) +{ + char *filename = NULL; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + struct smb2_handle testdirh = {{0}}; + struct timespec ts = { .tv_sec = t }; + uint64_t nttime; + union smb_fileinfo gi; + union smb_setfileinfo si; + struct smb2_find find; + unsigned int count; + union smb_search_data *d; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + status = torture_smb2_testdir(tree, BASEDIR, &testdirh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + + filename = talloc_asprintf(tctx, "%s\\%s", BASEDIR, fname); + torture_assert_not_null_goto(tctx, filename, ret, done, + "talloc_asprintf failed\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = filename, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + si = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = handle, + }; + + nttime = full_timespec_to_nt_time(&ts); + si.basic_info.in.create_time = nttime; + si.basic_info.in.write_time = nttime; + si.basic_info.in.change_time = nttime; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + gi = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = handle, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + torture_comment(tctx, "Got: create: %s, write: %s, change: %s\n", + nt_time_string(tctx, gi.basic_info.out.create_time), + nt_time_string(tctx, gi.basic_info.out.write_time), + nt_time_string(tctx, gi.basic_info.out.change_time)); + + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.create_time, + ret, done, + "Wrong create time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.write_time, + ret, done, + "Wrong write time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.change_time, + ret, done, + "Wrong change time\n"); + + find = (struct smb2_find) { + .in.file.handle = testdirh, + .in.pattern = fname, + .in.max_response_size = 0x1000, + .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, + }; + + status = smb2_find_level(tree, tree, &find, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_find_level failed\n"); + + torture_assert_u64_equal_goto(tctx, + nttime, + d[0].id_both_directory_info.create_time, + ret, done, + "Wrong create time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + d[0].id_both_directory_info.write_time, + ret, done, + "Wrong write time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + d[0].id_both_directory_info.change_time, + ret, done, + "Wrong change time\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = filename, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + gi = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = handle, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + torture_comment(tctx, "Got: create: %s, write: %s, change: %s\n", + nt_time_string(tctx, gi.basic_info.out.create_time), + nt_time_string(tctx, gi.basic_info.out.write_time), + nt_time_string(tctx, gi.basic_info.out.change_time)); + + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.create_time, + ret, done, + "Wrong create time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.write_time, + ret, done, + "Wrong write time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.change_time, + ret, done, + "Wrong change time\n"); + + find = (struct smb2_find) { + .in.continue_flags = SMB2_CONTINUE_FLAG_RESTART, + .in.file.handle = testdirh, + .in.pattern = fname, + .in.max_response_size = 0x1000, + .in.level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO, + }; + + status = smb2_find_level(tree, tree, &find, &count, &d); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_find_level failed\n"); + + torture_assert_u64_equal_goto(tctx, + nttime, + d[0].id_both_directory_info.create_time, + ret, done, + "Wrong create time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + d[0].id_both_directory_info.write_time, + ret, done, + "Wrong write time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + d[0].id_both_directory_info.change_time, + ret, done, + "Wrong change time\n"); + + status = smb2_util_close(tree, handle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(handle); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + if (!smb2_util_handle_empty(testdirh)) { + smb2_util_close(tree, testdirh); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_time_t_15032385535(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_15032385535.txt", + 15032385535 /* >> INT32_MAX, limit on ext */); +} + +static bool test_time_t_10000000000(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_10000000000.txt", + 10000000000 /* >> INT32_MAX */); +} + +static bool test_time_t_4294967295(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_4294967295.txt", + 4294967295 /* INT32_MAX */); +} + +static bool test_time_t_1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_1.txt", 1); +} + +static bool test_time_t_0(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_0.txt", 0); +} + +static bool test_time_t_minus_1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_-1.txt", -1); +} + +static bool test_time_t_minus_2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_-2.txt", -2); +} + +static bool test_time_t_1968(struct torture_context *tctx, + struct smb2_tree *tree) +{ + return test_time_t(tctx, tree, "test_time_t_1968.txt", + -63158400 /* 1968 */); +} + +static bool test_freeze_thaw(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *filename = BASEDIR "\\test_freeze_thaw"; + struct smb2_create cr; + struct smb2_handle handle = {{0}}; + struct smb2_handle testdirh = {{0}}; + struct timespec ts = { .tv_sec = time(NULL) }; + uint64_t nttime; + union smb_fileinfo gi; + union smb_setfileinfo si; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + + status = torture_smb2_testdir(tree, BASEDIR, &testdirh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS, + .in.fname = filename, + }; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + handle = cr.out.file.handle; + + si = (union smb_setfileinfo) { + .basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION, + .basic_info.in.file.handle = handle, + }; + + /* + * Step 1: + * First set timestamps of testfile to current time + */ + + nttime = full_timespec_to_nt_time(&ts); + si.basic_info.in.create_time = nttime; + si.basic_info.in.write_time = nttime; + si.basic_info.in.change_time = nttime; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + gi = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = handle, + }; + + /* + * Step 2: + * Verify timestamps are indeed set to the value in "nttime". + */ + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + torture_comment(tctx, "Got: create: %s, write: %s, change: %s\n", + nt_time_string(tctx, gi.basic_info.out.create_time), + nt_time_string(tctx, gi.basic_info.out.write_time), + nt_time_string(tctx, gi.basic_info.out.change_time)); + + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.create_time, + ret, done, + "Wrong create time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.write_time, + ret, done, + "Wrong write time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.change_time, + ret, done, + "Wrong change time\n"); + + /* + * Step 3: + * First set timestamps with NTTIME_FREEZE, must not change any + * timestamp value. + */ + + si.basic_info.in.create_time = NTTIME_FREEZE; + si.basic_info.in.write_time = NTTIME_FREEZE; + si.basic_info.in.change_time = NTTIME_FREEZE; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + gi = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = handle, + }; + + /* + * Step 4: + * Verify timestamps are unmodified from step 2. + */ + + gi = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = handle, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + torture_comment(tctx, "Got: create: %s, write: %s, change: %s\n", + nt_time_string(tctx, gi.basic_info.out.create_time), + nt_time_string(tctx, gi.basic_info.out.write_time), + nt_time_string(tctx, gi.basic_info.out.change_time)); + + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.create_time, + ret, done, + "Wrong create time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.write_time, + ret, done, + "Wrong write time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.change_time, + ret, done, + "Wrong change time\n"); + + /* + * Step 5: + * First set timestamps with NTTIME_THAW, must not change any timestamp + * value. + */ + + si.basic_info.in.create_time = NTTIME_THAW; + si.basic_info.in.write_time = NTTIME_THAW; + si.basic_info.in.change_time = NTTIME_THAW; + + status = smb2_setinfo_file(tree, &si); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + gi = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = handle, + }; + + /* + * Step 6: + * Verify timestamps are unmodified from step 2. + */ + + gi = (union smb_fileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + .generic.in.file.handle = handle, + }; + + status = smb2_getinfo_file(tree, tctx, &gi); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_getinfo_file failed\n"); + + torture_comment(tctx, "Got: create: %s, write: %s, change: %s\n", + nt_time_string(tctx, gi.basic_info.out.create_time), + nt_time_string(tctx, gi.basic_info.out.write_time), + nt_time_string(tctx, gi.basic_info.out.change_time)); + + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.create_time, + ret, done, + "Wrong create time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.write_time, + ret, done, + "Wrong write time\n"); + torture_assert_u64_equal_goto(tctx, + nttime, + gi.basic_info.out.change_time, + ret, done, + "Wrong change time\n"); + +done: + if (!smb2_util_handle_empty(handle)) { + smb2_util_close(tree, handle); + } + if (!smb2_util_handle_empty(testdirh)) { + smb2_util_close(tree, testdirh); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_write_vs_seteof(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTTIME create_time; + NTTIME set_time; + union smb_fileinfo finfo; + union smb_setfileinfo setinfo; + struct smb2_close c; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Check writetime hasn't been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != set_time (wrong!)\n"); + + torture_comment(tctx, "Setinfo EOF on file-handle 1," + " should flush pending writetime update\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION, + }; + setinfo.end_of_file_info.in.file.handle = h1; + setinfo.end_of_file_info.in.size = 1; /* same size! */ + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Check writetime has been updated " + "by the setinfo EOF\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + if (!(finfo.all_info.out.write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "setinfo EOF hasn't updated writetime\n"); + } + + torture_comment(tctx, "Open file-handle 2\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FILE_WRITE_ATTRIBUTE, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h2 = cr.out.file.handle; + + torture_comment(tctx, "Set write time on file-handle 2\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = SMB_QFILEINFO_BASIC_INFORMATION, + }; + setinfo.generic.in.file.handle = h2; + unix_to_nt_time(&set_time, time(NULL) + 86400); + setinfo.basic_info.in.write_time = set_time; + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + status = smb2_util_close(tree, h2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h2); + + torture_comment(tctx, "Close file-handle 1, write-time should not be updated\n"); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + torture_assert_nttime_equal(tctx, + c.out.write_time, + set_time, + "Writetime != set_time (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_write_vs_flush(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + union smb_fileinfo finfo; + struct smb2_flush f; + struct smb2_close c; + NTTIME create_time; + NTTIME flush_time; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Check writetime hasn't been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != create_time (wrong!)\n"); + + torture_comment(tctx, "Flush file, " + "should flush pending writetime update\n"); + + f = (struct smb2_flush) { + .in.file.handle = h1, + }; + + status = smb2_flush(tree, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "flush failed\n"); + + torture_comment(tctx, "Check writetime has been updated " + "by the setinfo EOF\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + flush_time = finfo.all_info.out.write_time; + if (!(flush_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "flush hasn't updated writetime\n"); + } + + torture_comment(tctx, "Close file-handle 1, write-time should not be updated\n"); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + torture_assert_nttime_equal(tctx, + c.out.write_time, + flush_time, + "writetime != flushtime (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_write_vs_setbasic_do(struct torture_context *tctx, + struct smb2_tree *tree, + union smb_setfileinfo *setinfo, + bool expect_update) +{ + char *path = NULL; + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + NTTIME create_time; + union smb_fileinfo finfo; + NTSTATUS status; + bool ret = true; + + torture_comment(tctx, "Create testfile\n"); + + path = talloc_asprintf(tree, BASEDIR "\\" FNAME ".%" PRIu32, + generate_random()); + torture_assert_not_null_goto(tctx, path, ret, done, "OOM\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = path, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + + torture_comment(tctx, "Write to file\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Get timestamps\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != create_time (wrong!)\n"); + + torture_comment(tctx, "Set timestamps\n"); + + setinfo->end_of_file_info.in.file.handle = h1; + status = smb2_setinfo_file(tree, setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Check timestamps\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + if (expect_update) { + if (!(finfo.all_info.out.write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "setinfo basicinfo " + "hasn't updated writetime\n"); + } + } else { + if (finfo.all_info.out.write_time != create_time) { + ret = false; + torture_fail_goto(tctx, done, "setinfo basicinfo " + "hasn't updated writetime\n"); + } + } + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + status = smb2_util_unlink(tree, path); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + +done: + TALLOC_FREE(path); + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + return ret; +} + +static bool test_delayed_write_vs_setbasic(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle h1 = {{0}}; + union smb_setfileinfo setinfo; + time_t t = time(NULL) - 86400; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + /* + * Yes, this is correct, tested against Windows 2016: even if all + * timestamp fields are 0, a pending write time is flushed. + */ + torture_comment(tctx, "Test: setting all-0 timestamps flushes?\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_SFILEINFO_BASIC_INFORMATION, + }; + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting create_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.create_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting access_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.access_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting change_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.change_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + +done: + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_1write(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + union smb_fileinfo finfo; + struct smb2_close c; + NTTIME create_time; + NTTIME write_time; + NTTIME close_time; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + sleep(3); + + torture_comment(tctx, "Check writetime has been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + write_time = finfo.all_info.out.write_time; + + if (!(write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, + "Write-time not updated (wrong!)\n"); + } + + torture_comment(tctx, "Close file-handle 1\n"); + sleep(1); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + close_time = c.out.write_time; + + torture_assert_nttime_equal(tctx, close_time, write_time, + "Writetime != close_time (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_2write(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + union smb_fileinfo finfo; + struct smb2_close c; + NTTIME create_time; + NTTIME write_time; + NTTIME write_time2; + struct timespec now; + NTTIME send_close_time; + NTTIME close_time; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + sleep(3); + + torture_comment(tctx, "Check writetime has been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + write_time = finfo.all_info.out.write_time; + + if (!(write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, + "Write-time not updated (wrong!)\n"); + } + + torture_comment(tctx, "Write a second time\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + sleep(3); + + torture_comment(tctx, "Check writetime has NOT been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + write_time2 = finfo.all_info.out.write_time; + + torture_assert_nttime_equal(tctx, write_time2, write_time, + "second write updated write-time (wrong!)\n"); + + torture_comment(tctx, "Close file-handle 1\n"); + sleep(2); + + now = timespec_current(); + send_close_time = full_timespec_to_nt_time(&now); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + close_time = c.out.write_time; + + if (!(close_time > send_close_time)) { + ret = false; + torture_fail_goto(tctx, done, + "Write-time not updated (wrong!)\n"); + } + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + basic testing of SMB2 timestamps +*/ +struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "timestamps"); + + torture_suite_add_1smb2_test(suite, "test_close_not_attrib", test_close_no_attrib); + torture_suite_add_1smb2_test(suite, "time_t_15032385535", test_time_t_15032385535); + torture_suite_add_1smb2_test(suite, "time_t_10000000000", test_time_t_10000000000); + torture_suite_add_1smb2_test(suite, "time_t_4294967295", test_time_t_4294967295); + torture_suite_add_1smb2_test(suite, "time_t_1", test_time_t_1); + torture_suite_add_1smb2_test(suite, "time_t_0", test_time_t_0); + torture_suite_add_1smb2_test(suite, "time_t_-1", test_time_t_minus_1); + torture_suite_add_1smb2_test(suite, "time_t_-2", test_time_t_minus_2); + torture_suite_add_1smb2_test(suite, "time_t_1968", test_time_t_1968); + torture_suite_add_1smb2_test(suite, "freeze-thaw", test_freeze_thaw); + + /* + * Testing of delayed write-time udpates + */ + torture_suite_add_1smb2_test(suite, "delayed-write-vs-seteof", test_delayed_write_vs_seteof); + torture_suite_add_1smb2_test(suite, "delayed-write-vs-flush", test_delayed_write_vs_flush); + torture_suite_add_1smb2_test(suite, "delayed-write-vs-setbasic", test_delayed_write_vs_setbasic); + torture_suite_add_1smb2_test(suite, "delayed-1write", test_delayed_1write); + torture_suite_add_1smb2_test(suite, "delayed-2write", test_delayed_2write); + + suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); + + return suite; +} + +/* + * This test shows that Windows has a timestamp resolution of ~15ms. When so + * when a smaller amount of time than that has passed it's not necessarily + * detectable on a Windows 2019 and newer who implement immediate timestamp + * updates. + * + * Note that this test relies on a low latency SMB connection. Even with a low + * latency connection of eg 1m there's a chance of 1/15 that the first part of + * the test expecting no timestamp change fails as the writetime is updated. + * + * Due to this timing dependency this test is skipped in Samba CI, but it is + * preserved here for future SMB2 timestamps behaviour archealogists. + * + * See also: https://lists.samba.org/archive/cifs-protocol/2019-December/003358.html + */ +static bool test_timestamp_resolution1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_fileinfo finfo1; + const char *fname = BASEDIR "\\" FNAME; + struct smb2_create cr; + struct smb2_handle h = {{0}}; + struct smb2_close cl; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Write without delay, expect no " + "write-time change\n"); + + smb2_generic_create(&cr, NULL, false, fname, + NTCREATEX_DISP_CREATE, + smb2_util_oplock_level(""), 0, 0); + status = smb2_create(tree, tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h = cr.out.file.handle; + + finfo1 = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h, + }; + status = smb2_getinfo_file(tree, tree, &finfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + status = smb2_util_write(tree, h, "123456789", 0, 9); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + cl = (struct smb2_close) { + .in.file.handle = h, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h); + + torture_comment(tctx, "Initial: %s\nClose: %s\n", + nt_time_string(tctx, finfo1.basic_info.out.write_time), + nt_time_string(tctx, cl.out.write_time)); + + torture_assert_u64_equal_goto(tctx, + finfo1.basic_info.out.write_time, + cl.out.write_time, + ret, done, + "Write time changed (wrong!)\n"); + + torture_comment(tctx, "Write with 20 ms delay, expect " + "write-time change\n"); + + smb2_generic_create(&cr, NULL, false, fname, + NTCREATEX_DISP_OPEN, + smb2_util_oplock_level(""), 0, 0); + status = smb2_create(tree, tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h = cr.out.file.handle; + + finfo1 = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h, + }; + status = smb2_getinfo_file(tree, tree, &finfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + smb_msleep(20); + + status = smb2_util_write(tree, h, "123456789", 0, 9); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + cl = (struct smb2_close) { + .in.file.handle = h, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h); + + torture_comment(tctx, "Initial: %s\nClose: %s\n", + nt_time_string(tctx, finfo1.basic_info.out.write_time), + nt_time_string(tctx, cl.out.write_time)); + + torture_assert_u64_not_equal_goto( + tctx, + finfo1.basic_info.out.write_time, + cl.out.write_time, + ret, done, + "Write time did not change (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h)) { + smb2_util_close(tree, h); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + basic testing of SMB2 timestamps +*/ +struct torture_suite *torture_smb2_timestamp_resolution_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "timestamp_resolution"); + + torture_suite_add_1smb2_test(suite, "resolution1", test_timestamp_resolution1); + + suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); + + return suite; +} diff --git a/source4/torture/smb2/util.c b/source4/torture/smb2/util.c new file mode 100644 index 0000000..233f589 --- /dev/null +++ b/source4/torture/smb2/util.c @@ -0,0 +1,1045 @@ +/* + Unix SMB/CIFS implementation. + + helper functions for SMB2 test suite + + Copyright (C) Andrew Tridgell 2005 + + 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/>. +*/ + +#include "includes.h" +#include "libcli/security/security_descriptor.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" +#include "lib/cmdline/cmdline.h" +#include "system/time.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" +#include "lib/util/tevent_ntstatus.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "source4/torture/util.h" +#include "libcli/security/dom_sid.h" +#include "librpc/gen_ndr/lsa.h" +#include "libcli/util/clilsa.h" + + +/* + write to a file on SMB2 +*/ +NTSTATUS smb2_util_write(struct smb2_tree *tree, + struct smb2_handle handle, + const void *buf, off_t offset, size_t size) +{ + struct smb2_write w; + + ZERO_STRUCT(w); + w.in.file.handle = handle; + w.in.offset = offset; + w.in.data = data_blob_const(buf, size); + + return smb2_write(tree, &w); +} + +/* + create a complex file/dir using the SMB2 protocol +*/ +static NTSTATUS smb2_create_complex(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + struct smb2_handle *handle, + bool dir) +{ + TALLOC_CTX *tmp_ctx = talloc_new(tree); + char buf[7] = "abc"; + struct smb2_create io; + union smb_setfileinfo setfile; + union smb_fileinfo fileinfo; + time_t t = (time(NULL) & ~1); + NTSTATUS status; + + smb2_util_unlink(tree, fname); + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = fname; + if (dir) { + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.share_access &= ~NTCREATEX_SHARE_ACCESS_DELETE; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + } + + /* it seems vista is now fussier about alignment? */ + if (strchr(fname, ':') == NULL) { + /* setup some EAs */ + io.in.eas.num_eas = 2; + io.in.eas.eas = talloc_array(tmp_ctx, struct ea_struct, 2); + io.in.eas.eas[0].flags = 0; + io.in.eas.eas[0].name.s = "EAONE"; + io.in.eas.eas[0].value = data_blob_talloc(tmp_ctx, "VALUE1", 6); + io.in.eas.eas[1].flags = 0; + io.in.eas.eas[1].name.s = "SECONDEA"; + io.in.eas.eas[1].value = data_blob_talloc(tmp_ctx, "ValueTwo", 8); + } + + status = smb2_create(tree, tmp_ctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_EAS_NOT_SUPPORTED)) { + torture_comment( + tctx, "EAs not supported, creating: %s\n", fname); + io.in.eas.num_eas = 0; + status = smb2_create(tree, tmp_ctx, &io); + } + + talloc_free(tmp_ctx); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + if (!dir) { + status = smb2_util_write(tree, *handle, buf, 0, sizeof(buf)); + NT_STATUS_NOT_OK_RETURN(status); + } + + /* make sure all the timestamps aren't the same, and are also + in different DST zones*/ + setfile.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + setfile.generic.in.file.handle = *handle; + + unix_to_nt_time(&setfile.basic_info.in.create_time, t + 9*30*24*60*60); + unix_to_nt_time(&setfile.basic_info.in.access_time, t + 6*30*24*60*60); + unix_to_nt_time(&setfile.basic_info.in.write_time, t + 3*30*24*60*60); + unix_to_nt_time(&setfile.basic_info.in.change_time, t + 1*30*24*60*60); + setfile.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL; + + status = smb2_setinfo_file(tree, &setfile); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to setup file times - %s\n", nt_errstr(status)); + return status; + } + + /* make sure all the timestamps aren't the same */ + fileinfo.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + fileinfo.generic.in.file.handle = *handle; + + status = smb2_getinfo_file(tree, tree, &fileinfo); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to query file times - %s\n", nt_errstr(status)); + return status; + + } + +#define CHECK_TIME(field) do {\ + if (setfile.basic_info.in.field != fileinfo.all_info2.out.field) { \ + torture_comment(tctx, "(%s) " #field " not setup correctly: %s(%llu) => %s(%llu)\n", \ + __location__, \ + nt_time_string(tree, setfile.basic_info.in.field), \ + (unsigned long long)setfile.basic_info.in.field, \ + nt_time_string(tree, fileinfo.basic_info.out.field), \ + (unsigned long long)fileinfo.basic_info.out.field); \ + status = NT_STATUS_INVALID_PARAMETER; \ + } \ +} while (0) + + CHECK_TIME(create_time); + CHECK_TIME(access_time); + CHECK_TIME(write_time); + CHECK_TIME(change_time); + + return status; +} + +/* + create a complex file using the SMB2 protocol +*/ +NTSTATUS smb2_create_complex_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return smb2_create_complex(tctx, tree, fname, handle, false); +} + +/* + create a complex dir using the SMB2 protocol +*/ +NTSTATUS smb2_create_complex_dir(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return smb2_create_complex(tctx, tree, fname, handle, true); +} + +/* + show lots of information about a file +*/ +void torture_smb2_all_info(struct torture_context *tctx, + struct smb2_tree *tree, struct smb2_handle handle) +{ + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + union smb_fileinfo io; + + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = handle; + + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("getinfo failed - %s\n", nt_errstr(status))); + talloc_free(tmp_ctx); + return; + } + + torture_comment(tctx, "all_info for '%s'\n", io.all_info2.out.fname.s); + torture_comment(tctx, "\tcreate_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.create_time)); + torture_comment(tctx, "\taccess_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.access_time)); + torture_comment(tctx, "\twrite_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.write_time)); + torture_comment(tctx, "\tchange_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.change_time)); + torture_comment(tctx, "\tattrib: 0x%x\n", io.all_info2.out.attrib); + torture_comment(tctx, "\tunknown1: 0x%x\n", io.all_info2.out.unknown1); + torture_comment(tctx, "\talloc_size: %llu\n", (long long)io.all_info2.out.alloc_size); + torture_comment(tctx, "\tsize: %llu\n", (long long)io.all_info2.out.size); + torture_comment(tctx, "\tnlink: %u\n", io.all_info2.out.nlink); + torture_comment(tctx, "\tdelete_pending: %u\n", io.all_info2.out.delete_pending); + torture_comment(tctx, "\tdirectory: %u\n", io.all_info2.out.directory); + torture_comment(tctx, "\tfile_id: %llu\n", (long long)io.all_info2.out.file_id); + torture_comment(tctx, "\tea_size: %u\n", io.all_info2.out.ea_size); + torture_comment(tctx, "\taccess_mask: 0x%08x\n", io.all_info2.out.access_mask); + torture_comment(tctx, "\tposition: 0x%llx\n", (long long)io.all_info2.out.position); + torture_comment(tctx, "\tmode: 0x%llx\n", (long long)io.all_info2.out.mode); + + /* short name, if any */ + io.generic.level = RAW_FILEINFO_ALT_NAME_INFORMATION; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "\tshort name: '%s'\n", io.alt_name_info.out.fname.s); + } + + /* the EAs, if any */ + io.generic.level = RAW_FILEINFO_SMB2_ALL_EAS; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + int i; + for (i=0;i<io.all_eas.out.num_eas;i++) { + torture_comment(tctx, "\tEA[%d] flags=%d len=%d '%s'\n", i, + io.all_eas.out.eas[i].flags, + (int)io.all_eas.out.eas[i].value.length, + io.all_eas.out.eas[i].name.s); + } + } + + /* streams, if available */ + io.generic.level = RAW_FILEINFO_STREAM_INFORMATION; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + int i; + for (i=0;i<io.stream_info.out.num_streams;i++) { + torture_comment(tctx, "\tstream %d:\n", i); + torture_comment(tctx, "\t\tsize %ld\n", + (long)io.stream_info.out.streams[i].size); + torture_comment(tctx, "\t\talloc size %ld\n", + (long)io.stream_info.out.streams[i].alloc_size); + torture_comment(tctx, "\t\tname %s\n", io.stream_info.out.streams[i].stream_name.s); + } + } + + if (DEBUGLVL(1)) { + /* the security descriptor */ + io.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + io.query_secdesc.in.secinfo_flags = + SECINFO_OWNER|SECINFO_GROUP| + SECINFO_DACL; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(security_descriptor, io.query_secdesc.out.sd); + } + } + + talloc_free(tmp_ctx); +} + +/* + get granted access of a file handle +*/ +NTSTATUS torture_smb2_get_allinfo_access(struct smb2_tree *tree, + struct smb2_handle handle, + uint32_t *granted_access) +{ + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + union smb_fileinfo io; + + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = handle; + + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("getinfo failed - %s\n", nt_errstr(status))); + goto out; + } + + *granted_access = io.all_info2.out.access_mask; + +out: + talloc_free(tmp_ctx); + return status; +} + +/** + * open a smb2 tree connect + */ +bool torture_smb2_tree_connect(struct torture_context *tctx, + struct smb2_session *session, + TALLOC_CTX *mem_ctx, + struct smb2_tree **_tree) +{ + NTSTATUS status; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + const char *unc; + struct smb2_tree *tree; + struct tevent_req *subreq; + uint32_t timeout_msec; + + unc = talloc_asprintf(tctx, "\\\\%s\\%s", host, share); + torture_assert(tctx, unc != NULL, "talloc_asprintf"); + + tree = smb2_tree_init(session, mem_ctx, false); + torture_assert(tctx, tree != NULL, "smb2_tree_init"); + + timeout_msec = session->transport->options.request_timeout * 1000; + + subreq = smb2cli_tcon_send(tree, tctx->ev, + session->transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + 0, /* flags */ + unc); + torture_assert(tctx, subreq != NULL, "smb2cli_tcon_send"); + + torture_assert(tctx, + tevent_req_poll_ntstatus(subreq, tctx->ev, &status), + "tevent_req_poll_ntstatus"); + + status = smb2cli_tcon_recv(subreq); + TALLOC_FREE(subreq); + torture_assert_ntstatus_ok(tctx, status, "smb2cli_tcon_recv"); + + *_tree = tree; + + return true; +} + +/** + * do a smb2 session setup (without a tree connect) + */ +bool torture_smb2_session_setup(struct torture_context *tctx, + struct smb2_transport *transport, + uint64_t previous_session_id, + TALLOC_CTX *mem_ctx, + struct smb2_session **_session) +{ + NTSTATUS status; + struct smb2_session *session; + + session = smb2_session_init(transport, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + mem_ctx); + + if (session == NULL) { + return false; + } + + status = smb2_session_setup_spnego(session, + samba_cmdline_get_creds(), + previous_session_id); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "session setup failed: %s\n", nt_errstr(status)); + talloc_free(session); + return false; + } + + *_session = session; + + return true; +} + +/* + open a smb2 connection +*/ +bool torture_smb2_connection_ext(struct torture_context *tctx, + uint64_t previous_session_id, + const struct smbcli_options *options, + struct smb2_tree **tree) +{ + NTSTATUS status; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + const char *p = torture_setting_string(tctx, "unclist", NULL); + TALLOC_CTX *mem_ctx = NULL; + bool ok; + + if (p != NULL) { + char *host2 = NULL; + char *share2 = NULL; + + mem_ctx = talloc_new(tctx); + if (mem_ctx == NULL) { + return false; + } + + ok = torture_get_conn_index(tctx->conn_index++, mem_ctx, tctx, + &host2, &share2); + if (!ok) { + TALLOC_FREE(mem_ctx); + return false; + } + + host = host2; + share = share2; + } + + status = smb2_connect_ext(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + NULL, /* existing_conn */ + previous_session_id, + tree, + tctx->ev, + options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to connect to SMB2 share \\\\%s\\%s - %s\n", + host, share, nt_errstr(status)); + TALLOC_FREE(mem_ctx); + return false; + } + + TALLOC_FREE(mem_ctx); + return true; +} + +bool torture_smb2_connection(struct torture_context *tctx, struct smb2_tree **tree) +{ + bool ret; + struct smbcli_options options; + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + ret = torture_smb2_connection_ext(tctx, 0, &options, tree); + + return ret; +} + +/** + * SMB2 connect with share from soption + **/ +bool torture_smb2_con_share(struct torture_context *tctx, + const char *share, + struct smb2_tree **tree) +{ + struct smbcli_options options; + NTSTATUS status; + const char *host = torture_setting_string(tctx, "host", NULL); + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to connect to SMB2 share \\\\%s\\%s - %s\n", + host, share, nt_errstr(status)); + return false; + } + return true; +} + +/** + * SMB2 connect with share from soption + **/ +bool torture_smb2_con_sopt(struct torture_context *tctx, + const char *soption, + struct smb2_tree **tree) +{ + const char *share = torture_setting_string(tctx, soption, NULL); + + if (share == NULL) { + torture_comment(tctx, "No share for option %s\n", soption); + return false; + } + + return torture_smb2_con_share(tctx, share, tree); +} + +/* + create and return a handle to a test file + with a specific access mask +*/ +NTSTATUS torture_smb2_testfile_access(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle, + uint32_t desired_access) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = desired_access; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = fname; + + status = smb2_create(tree, tree, &io); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* + create and return a handle to a test file +*/ +NTSTATUS torture_smb2_testfile(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return torture_smb2_testfile_access(tree, fname, handle, + SEC_RIGHTS_FILE_ALL); +} + +/* + create and return a handle to a test file + with a specific access mask +*/ +NTSTATUS torture_smb2_open(struct smb2_tree *tree, + const char *fname, + uint32_t desired_access, + struct smb2_handle *handle) +{ + struct smb2_create io; + NTSTATUS status; + + io = (struct smb2_create) { + .in.fname = fname, + .in.desired_access = desired_access, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + }; + + status = smb2_create(tree, tree, &io); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* + create and return a handle to a test directory + with specific desired access +*/ +NTSTATUS torture_smb2_testdir_access(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle, + uint32_t desired_access) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = desired_access; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.fname = fname; + + status = smb2_create(tree, tree, &io); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* + create and return a handle to a test directory +*/ +NTSTATUS torture_smb2_testdir(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return torture_smb2_testdir_access(tree, fname, handle, + SEC_RIGHTS_DIR_ALL); +} + +/* + create a simple file using the SMB2 protocol +*/ +NTSTATUS smb2_create_simple_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + char buf[7] = "abc"; + NTSTATUS status; + + smb2_util_unlink(tree, fname); + status = torture_smb2_testfile_access(tree, + fname, handle, + SEC_FLAG_MAXIMUM_ALLOWED); + NT_STATUS_NOT_OK_RETURN(status); + + status = smb2_util_write(tree, *handle, buf, 0, sizeof(buf)); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +/* + create a simple file using SMB2. +*/ +NTSTATUS torture_setup_simple_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname) +{ + struct smb2_handle handle; + NTSTATUS status = smb2_create_simple_file(tctx, tree, fname, &handle); + NT_STATUS_NOT_OK_RETURN(status); + return smb2_util_close(tree, handle); +} + +/* + create a complex file using SMB2, to make it easier to + find fields in SMB2 getinfo levels +*/ +NTSTATUS torture_setup_complex_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname) +{ + struct smb2_handle handle; + NTSTATUS status = smb2_create_complex_file(tctx, tree, fname, &handle); + NT_STATUS_NOT_OK_RETURN(status); + return smb2_util_close(tree, handle); +} + + +/* + create a complex dir using SMB2, to make it easier to + find fields in SMB2 getinfo levels +*/ +NTSTATUS torture_setup_complex_dir(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname) +{ + struct smb2_handle handle; + NTSTATUS status = smb2_create_complex_dir(tctx, tree, fname, &handle); + NT_STATUS_NOT_OK_RETURN(status); + return smb2_util_close(tree, handle); +} + + +/* + return a handle to the root of the share +*/ +NTSTATUS smb2_util_roothandle(struct smb2_tree *tree, struct smb2_handle *handle) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_DIR_READ_ATTRIBUTE | SEC_DIR_LIST; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_ASYNC_ALERT; + io.in.fname = ""; + + status = smb2_create(tree, tree, &io); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* Comparable to torture_setup_dir, but for SMB2. */ +bool smb2_util_setup_dir(struct torture_context *tctx, struct smb2_tree *tree, + const char *dname) +{ + NTSTATUS status; + + /* XXX: smb_raw_exit equivalent? + smb_raw_exit(cli->session); */ + if (smb2_deltree(tree, dname) == -1) { + torture_result(tctx, TORTURE_ERROR, "Unable to deltree when setting up %s.\n", dname); + return false; + } + + status = smb2_util_mkdir(tree, dname); + if (NT_STATUS_IS_ERR(status)) { + torture_result(tctx, TORTURE_ERROR, "Unable to mkdir when setting up %s - %s\n", dname, + nt_errstr(status)); + return false; + } + + return true; +} + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +/* + * Helper function to verify a security descriptor, by querying + * and comparing against the passed in sd. + */ +bool smb2_util_verify_sd(TALLOC_CTX *tctx, struct smb2_tree *tree, + struct smb2_handle handle, struct security_descriptor *sd) +{ + NTSTATUS status; + bool ret = true; + union smb_fileinfo q = {}; + + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!security_acl_equal( + q.query_secdesc.out.sd->dacl, sd->dacl)) { + torture_warning(tctx, "%s: security descriptors don't match!\n", + __location__); + torture_warning(tctx, "got:\n"); + NDR_PRINT_DEBUG(security_descriptor, + q.query_secdesc.out.sd); + torture_warning(tctx, "expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + } + + done: + return ret; +} + +/* + * Helper function to verify attributes, by querying + * and comparing against the passed in attrib. + */ +bool smb2_util_verify_attrib(TALLOC_CTX *tctx, struct smb2_tree *tree, + struct smb2_handle handle, uint32_t attrib) +{ + NTSTATUS status; + bool ret = true; + union smb_fileinfo q = {}; + + q.standard.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + q.standard.in.file.handle = handle; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + q.all_info2.out.attrib &= ~(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_NONINDEXED); + + if (q.all_info2.out.attrib != attrib) { + torture_warning(tctx, "%s: attributes don't match! " + "got %x, expected %x\n", __location__, + (uint32_t)q.standard.out.attrib, + (uint32_t)attrib); + ret = false; + } + + done: + return ret; +} + + +uint32_t smb2_util_lease_state(const char *ls) +{ + uint32_t val = 0; + int i; + + for (i = 0; i < strlen(ls); i++) { + switch (ls[i]) { + case 'R': + val |= SMB2_LEASE_READ; + break; + case 'H': + val |= SMB2_LEASE_HANDLE; + break; + case 'W': + val |= SMB2_LEASE_WRITE; + break; + } + } + + return val; +} + +char *smb2_util_lease_state_string(TALLOC_CTX *mem_ctx, uint32_t ls) +{ + return talloc_asprintf(mem_ctx, "0x%0x (%s%s%s)", + (unsigned)ls, + ls & SMB2_LEASE_READ ? "R": "", + ls & SMB2_LEASE_HANDLE ? "H": "", + ls & SMB2_LEASE_WRITE ? "W": ""); +} + +uint32_t smb2_util_share_access(const char *sharemode) +{ + uint32_t val = NTCREATEX_SHARE_ACCESS_NONE; /* 0 */ + int i; + + for (i = 0; i < strlen(sharemode); i++) { + switch(sharemode[i]) { + case 'R': + val |= NTCREATEX_SHARE_ACCESS_READ; + break; + case 'W': + val |= NTCREATEX_SHARE_ACCESS_WRITE; + break; + case 'D': + val |= NTCREATEX_SHARE_ACCESS_DELETE; + break; + } + } + + return val; +} + +uint8_t smb2_util_oplock_level(const char *op) +{ + uint8_t val = SMB2_OPLOCK_LEVEL_NONE; + int i; + + for (i = 0; i < strlen(op); i++) { + switch (op[i]) { + case 's': + return SMB2_OPLOCK_LEVEL_II; + case 'x': + return SMB2_OPLOCK_LEVEL_EXCLUSIVE; + case 'b': + return SMB2_OPLOCK_LEVEL_BATCH; + default: + continue; + } + } + + return val; +} + +/** + * Helper functions to fill a smb2_create struct for several + * open scenarios. + */ +void smb2_generic_create_share(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint32_t disposition, + uint32_t share_access, + uint8_t oplock, uint64_t leasekey, + uint32_t leasestate) +{ + ZERO_STRUCT(*io); + io->in.security_flags = 0x00; + io->in.oplock_level = oplock; + io->in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + io->in.create_flags = 0x00000000; + io->in.reserved = 0x00000000; + io->in.desired_access = SEC_RIGHTS_FILE_ALL; + io->in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io->in.share_access = share_access; + io->in.create_disposition = disposition; + io->in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + io->in.fname = name; + + if (dir) { + io->in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io->in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io->in.create_disposition = NTCREATEX_DISP_CREATE; + } + + if (ls) { + ZERO_STRUCTPN(ls); + ls->lease_key.data[0] = leasekey; + ls->lease_key.data[1] = ~leasekey; + ls->lease_state = leasestate; + io->in.lease_request = ls; + } +} + +void smb2_generic_create(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint32_t disposition, + uint8_t oplock, uint64_t leasekey, + uint32_t leasestate) +{ + smb2_generic_create_share(io, ls, dir, name, disposition, + smb2_util_share_access("RWD"), + oplock, + leasekey, leasestate); +} + +void smb2_lease_create_share(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint32_t share_access, + uint64_t leasekey, uint32_t leasestate) +{ + smb2_generic_create_share(io, ls, dir, name, NTCREATEX_DISP_OPEN_IF, + share_access, SMB2_OPLOCK_LEVEL_LEASE, + leasekey, leasestate); +} + +void smb2_lease_create(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint64_t leasekey, + uint32_t leasestate) +{ + smb2_lease_create_share(io, ls, dir, name, + smb2_util_share_access("RWD"), + leasekey, leasestate); +} + +void smb2_lease_v2_create_share(struct smb2_create *io, + struct smb2_lease *ls, + bool dir, + const char *name, + uint32_t share_access, + uint64_t leasekey, + const uint64_t *parentleasekey, + uint32_t leasestate, + uint16_t lease_epoch) +{ + smb2_generic_create_share(io, NULL, dir, name, NTCREATEX_DISP_OPEN_IF, + share_access, SMB2_OPLOCK_LEVEL_LEASE, 0, 0); + + if (ls) { + ZERO_STRUCT(*ls); + ls->lease_key.data[0] = leasekey; + ls->lease_key.data[1] = ~leasekey; + ls->lease_state = leasestate; + if (parentleasekey != NULL) { + ls->lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET; + ls->parent_lease_key.data[0] = *parentleasekey; + ls->parent_lease_key.data[1] = ~(*parentleasekey); + } + ls->lease_epoch = lease_epoch; + io->in.lease_request_v2 = ls; + } +} + +void smb2_lease_v2_create(struct smb2_create *io, + struct smb2_lease *ls, + bool dir, + const char *name, + uint64_t leasekey, + const uint64_t *parentleasekey, + uint32_t leasestate, + uint16_t lease_epoch) +{ + smb2_lease_v2_create_share(io, ls, dir, name, + smb2_util_share_access("RWD"), + leasekey, parentleasekey, + leasestate, lease_epoch); +} + + +void smb2_oplock_create_share(struct smb2_create *io, const char *name, + uint32_t share_access, uint8_t oplock) +{ + smb2_generic_create_share(io, NULL, false, name, NTCREATEX_DISP_OPEN_IF, + share_access, oplock, 0, 0); +} +void smb2_oplock_create(struct smb2_create *io, const char *name, uint8_t oplock) +{ + smb2_oplock_create_share(io, name, smb2_util_share_access("RWD"), + oplock); +} + +/* + a wrapper around smblsa_sid_check_privilege, that tries to take + account of the fact that the lsa privileges calls don't expand + group memberships, using an explicit check for administrator. There + must be a better way ... + */ +NTSTATUS torture_smb2_check_privilege(struct smb2_tree *tree, + const char *sid_str, + const char *privilege) +{ + struct dom_sid *sid = NULL; + TALLOC_CTX *tmp_ctx = NULL; + uint32_t rid; + NTSTATUS status; + + tmp_ctx = talloc_new(tree); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sid = dom_sid_parse_talloc(tmp_ctx, sid_str); + if (sid == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_SID; + } + + status = dom_sid_split_rid(tmp_ctx, sid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + if (rid == DOMAIN_RID_ADMINISTRATOR) { + /* assume the administrator has them all */ + TALLOC_FREE(tmp_ctx); + return NT_STATUS_OK; + } + + talloc_free(tmp_ctx); + + return smb2lsa_sid_check_privilege(tree, sid_str, privilege); +} diff --git a/source4/torture/smb2/wscript_build b/source4/torture/smb2/wscript_build new file mode 100644 index 0000000..9343426 --- /dev/null +++ b/source4/torture/smb2/wscript_build @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +bld.SAMBA_MODULE('TORTURE_SMB2', + source=''' + acls.c + attr.c + block.c + charset.c + compound.c + connect.c + create.c + credits.c + delete-on-close.c + deny.c + dir.c + dosmode.c + durable_open.c + durable_v2_open.c + ea.c + getinfo.c + ioctl.c + lease.c + lease_break_handler.c + lock.c + max_allowed.c + mangle.c + maxfid.c + maxwrite.c + mkdir.c + multichannel.c + oplock_break_handler.c + notify.c + notify_disabled.c + oplock.c + read.c + read_write.c + rename.c + replay.c + scan.c + secleak.c + session.c + sessid.c + setinfo.c + sharemode.c + smb2.c + streams.c + samba3misc.c + tcon.c + timestamps.c + util.c + ''', + subsystem='smbtorture', + deps='LIBCLI_SMB2 torture NDR_IOCTL CMDLINE_S4', + internal_module=True, + autoproto='proto.h', + init_function='torture_smb2_init' + ) + |