summaryrefslogtreecommitdiffstats
path: root/source4/torture/smb2
diff options
context:
space:
mode:
Diffstat (limited to 'source4/torture/smb2')
-rw-r--r--source4/torture/smb2/acls.c3340
-rw-r--r--source4/torture/smb2/attr.c710
-rw-r--r--source4/torture/smb2/bench.c1376
-rw-r--r--source4/torture/smb2/block.c446
-rw-r--r--source4/torture/smb2/block.h43
-rw-r--r--source4/torture/smb2/charset.c235
-rw-r--r--source4/torture/smb2/compound.c2595
-rw-r--r--source4/torture/smb2/connect.c257
-rw-r--r--source4/torture/smb2/create.c3629
-rw-r--r--source4/torture/smb2/credits.c268
-rw-r--r--source4/torture/smb2/delete-on-close.c762
-rw-r--r--source4/torture/smb2/deny.c526
-rw-r--r--source4/torture/smb2/dir.c1606
-rw-r--r--source4/torture/smb2/dosmode.c254
-rw-r--r--source4/torture/smb2/durable_open.c2872
-rw-r--r--source4/torture/smb2/durable_v2_open.c2371
-rw-r--r--source4/torture/smb2/ea.c152
-rw-r--r--source4/torture/smb2/getinfo.c951
-rw-r--r--source4/torture/smb2/ioctl.c7552
-rw-r--r--source4/torture/smb2/lease.c4819
-rw-r--r--source4/torture/smb2/lease_break_handler.c161
-rw-r--r--source4/torture/smb2/lease_break_handler.h134
-rw-r--r--source4/torture/smb2/lock.c3513
-rw-r--r--source4/torture/smb2/mangle.c341
-rw-r--r--source4/torture/smb2/max_allowed.c248
-rw-r--r--source4/torture/smb2/maxfid.c149
-rw-r--r--source4/torture/smb2/maxwrite.c137
-rw-r--r--source4/torture/smb2/mkdir.c105
-rw-r--r--source4/torture/smb2/multichannel.c2743
-rw-r--r--source4/torture/smb2/notify.c2786
-rw-r--r--source4/torture/smb2/notify_disabled.c120
-rw-r--r--source4/torture/smb2/oplock.c5405
-rw-r--r--source4/torture/smb2/oplock_break_handler.c169
-rw-r--r--source4/torture/smb2/oplock_break_handler.h57
-rw-r--r--source4/torture/smb2/read.c573
-rw-r--r--source4/torture/smb2/read_write.c361
-rw-r--r--source4/torture/smb2/rename.c1751
-rw-r--r--source4/torture/smb2/replay.c5515
-rw-r--r--source4/torture/smb2/samba3misc.c189
-rw-r--r--source4/torture/smb2/scan.c265
-rw-r--r--source4/torture/smb2/secleak.c91
-rw-r--r--source4/torture/smb2/sessid.c100
-rw-r--r--source4/torture/smb2/session.c5670
-rw-r--r--source4/torture/smb2/setinfo.c410
-rw-r--r--source4/torture/smb2/sharemode.c755
-rw-r--r--source4/torture/smb2/smb2.c230
-rw-r--r--source4/torture/smb2/streams.c2424
-rw-r--r--source4/torture/smb2/tcon.c146
-rw-r--r--source4/torture/smb2/timestamps.c1344
-rw-r--r--source4/torture/smb2/util.c1045
-rw-r--r--source4/torture/smb2/wscript_build59
51 files changed, 71760 insertions, 0 deletions
diff --git a/source4/torture/smb2/acls.c b/source4/torture/smb2/acls.c
new file mode 100644
index 0000000..019886e
--- /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 owner doesn't affect SD flags.
+ *
+ * Do this by first changing owner to world and then
+ * back to the original owner. 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
+ * process 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 disposition\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..bc474d2
--- /dev/null
+++ b/source4/torture/smb2/attr.c
@@ -0,0 +1,710 @@
+/*
+ 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_winattr2(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ const char *fname = "winattr2.file";
+ struct smb2_create c = {0};
+ NTSTATUS status;
+ bool ret = true;
+
+ smb2_util_unlink(tree, fname);
+
+ /* Create a file with FILE_ATTRIBUTE_ARCHIVE */
+ c = (struct smb2_create) {
+ .in.desired_access = SEC_FILE_READ_DATA,
+ .in.file_attributes = FILE_ATTRIBUTE_ARCHIVE,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_NONE,
+ .in.create_disposition = NTCREATEX_DISP_OPEN_IF,
+ .in.fname = fname,
+ };
+
+ status = smb2_create(tree, tctx, &c);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_create failed\n");
+
+ status = smb2_util_close(tree, c.out.file.handle);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_util_close failed\n");
+
+ /* Reopen file with different attributes */
+ c = (struct smb2_create) {
+ .in.desired_access = SEC_FILE_READ_DATA,
+ .in.file_attributes = FILE_ATTRIBUTE_ARCHIVE |
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_READONLY,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_NONE,
+ .in.create_disposition = NTCREATEX_DISP_OPEN_IF,
+ .in.fname = fname,
+ };
+
+ status = smb2_create(tree, tctx, &c);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_create failed\n");
+
+ status = smb2_util_close(tree, c.out.file.handle);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_util_close failed\n");
+
+ torture_assert_int_equal_goto(tctx,
+ c.out.file_attr,
+ FILE_ATTRIBUTE_ARCHIVE,
+ ret, done,
+ "Wrong attributes\n");
+
+done:
+ smb2_util_unlink(tree, fname);
+ 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/bench.c b/source4/torture/smb2/bench.c
new file mode 100644
index 0000000..a91ca6c
--- /dev/null
+++ b/source4/torture/smb2/bench.c
@@ -0,0 +1,1376 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ SMB2 bench test suite
+
+ Copyright (C) Stefan Metzmacher 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 "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;
+ size_t pending_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;
+ uint64_t total_finished;
+ uint64_t max_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, "smb2cli_echo_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_finished += 1;
+ loop->total_latency += latency;
+
+ if (latency < loop->min_latency) {
+ loop->min_latency = latency;
+ }
+
+ if (latency > loop->max_latency) {
+ loop->max_latency = latency;
+ }
+
+ if (loop->total_finished >= loop->max_finished) {
+ if (state->pending_loops > 0) {
+ state->pending_loops -= 1;
+ }
+ if (state->pending_loops == 0) {
+ goto asserted;
+ }
+ }
+
+ 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 looplimit = torture_setting_int(tctx, "looplimit", -1);
+ 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++;
+ if (looplimit != -1) {
+ loop->max_finished = looplimit;
+ } else {
+ loop->max_finished = UINT64_MAX;
+ }
+ 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();
+ state->pending_loops = state->num_loops;
+
+ 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;
+}
+
+/*
+ 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;
+}
+
+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;
+}
+
+/*
+ stress testing read iops
+ */
+
+struct test_smb2_bench_read_conn;
+struct test_smb2_bench_read_loop;
+
+struct test_smb2_bench_read_state {
+ struct torture_context *tctx;
+ size_t num_conns;
+ struct test_smb2_bench_read_conn *conns;
+ size_t num_loops;
+ struct test_smb2_bench_read_loop *loops;
+ size_t pending_loops;
+ uint32_t io_size;
+ 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_read_conn {
+ struct test_smb2_bench_read_state *state;
+ int idx;
+ struct smb2_tree *tree;
+};
+
+struct test_smb2_bench_read_loop {
+ struct test_smb2_bench_read_state *state;
+ struct test_smb2_bench_read_conn *conn;
+ int idx;
+ struct tevent_immediate *im;
+ char *fname;
+ struct smb2_handle handle;
+ struct tevent_req *req;
+ struct timeval starttime;
+ uint64_t num_started;
+ uint64_t num_finished;
+ uint64_t total_finished;
+ uint64_t max_finished;
+ double total_latency;
+ double min_latency;
+ double max_latency;
+ NTSTATUS error;
+};
+
+static void test_smb2_bench_read_loop_do(
+ struct test_smb2_bench_read_loop *loop);
+
+static void test_smb2_bench_read_loop_start(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct test_smb2_bench_read_loop *loop =
+ (struct test_smb2_bench_read_loop *)
+ private_data;
+
+ test_smb2_bench_read_loop_do(loop);
+}
+
+static void test_smb2_bench_read_loop_done(struct tevent_req *req);
+
+static void test_smb2_bench_read_loop_do(
+ struct test_smb2_bench_read_loop *loop)
+{
+ struct test_smb2_bench_read_state *state = loop->state;
+ uint32_t timeout_msec;
+
+ timeout_msec = loop->conn->tree->session->transport->options.request_timeout * 1000;
+
+ loop->num_started += 1;
+ loop->starttime = timeval_current();
+ loop->req = smb2cli_read_send(state->loops,
+ state->tctx->ev,
+ loop->conn->tree->session->transport->conn,
+ timeout_msec,
+ loop->conn->tree->session->smbXcli,
+ loop->conn->tree->smbXcli,
+ state->io_size, /* length */
+ 0, /* offset */
+ loop->handle.data[0],/* fid_persistent */
+ loop->handle.data[1],/* fid_volatile */
+ state->io_size, /* minimum_count */
+ 0); /* remaining_bytes */
+ torture_assert_goto(state->tctx, loop->req != NULL,
+ state->ok, asserted, "smb2cli_read_send");
+
+ tevent_req_set_callback(loop->req,
+ test_smb2_bench_read_loop_done,
+ loop);
+ return;
+asserted:
+ state->stop = true;
+}
+
+static void test_smb2_bench_read_loop_done(struct tevent_req *req)
+{
+ struct test_smb2_bench_read_loop *loop =
+ (struct test_smb2_bench_read_loop *)
+ _tevent_req_callback_data(req);
+ struct test_smb2_bench_read_state *state = loop->state;
+ double latency = timeval_elapsed(&loop->starttime);
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint8_t *data = NULL;
+ uint32_t data_length = 0;
+
+ torture_assert_goto(state->tctx, loop->req == req,
+ state->ok, asserted, __location__);
+ loop->error = smb2cli_read_recv(req, frame, &data, &data_length);
+ torture_assert_ntstatus_ok_goto(state->tctx, loop->error,
+ state->ok, asserted, __location__);
+ torture_assert_u32_equal_goto(state->tctx, data_length, state->io_size,
+ 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_finished += 1;
+ loop->total_latency += latency;
+
+ if (latency < loop->min_latency) {
+ loop->min_latency = latency;
+ }
+
+ if (latency > loop->max_latency) {
+ loop->max_latency = latency;
+ }
+
+ if (loop->total_finished >= loop->max_finished) {
+ if (state->pending_loops > 0) {
+ state->pending_loops -= 1;
+ }
+ if (state->pending_loops == 0) {
+ goto asserted;
+ }
+ }
+
+ TALLOC_FREE(frame);
+ test_smb2_bench_read_loop_do(loop);
+ return;
+asserted:
+ state->stop = true;
+ TALLOC_FREE(frame);
+}
+
+static void test_smb2_bench_read_progress(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct test_smb2_bench_read_state *state =
+ (struct test_smb2_bench_read_state *)private_data;
+ uint64_t num_reads = 0;
+ double total_read_latency = 0;
+ double min_read_latency = 0;
+ double max_read_latency = 0;
+ double avs_read_latency = 0;
+ size_t i;
+
+ state->timecount += 1;
+
+ for (i=0;i<state->num_loops;i++) {
+ struct test_smb2_bench_read_loop *loop =
+ &state->loops[i];
+
+ num_reads += loop->num_finished;
+ total_read_latency += loop->total_latency;
+ if (min_read_latency == 0.0 && loop->min_latency != 0.0) {
+ min_read_latency = loop->min_latency;
+ }
+ if (loop->min_latency < min_read_latency) {
+ min_read_latency = loop->min_latency;
+ }
+ if (max_read_latency == 0.0) {
+ max_read_latency = loop->max_latency;
+ }
+ if (loop->max_latency > max_read_latency) {
+ max_read_latency = loop->max_latency;
+ }
+ loop->num_finished = 0;
+ loop->total_latency = 0.0;
+ }
+
+ state->num_finished += num_reads;
+ state->total_latency += total_read_latency;
+ if (state->min_latency == 0.0 && min_read_latency != 0.0) {
+ state->min_latency = min_read_latency;
+ }
+ if (min_read_latency < state->min_latency) {
+ state->min_latency = min_read_latency;
+ }
+ if (state->max_latency == 0.0) {
+ state->max_latency = max_read_latency;
+ }
+ if (max_read_latency > state->max_latency) {
+ state->max_latency = max_read_latency;
+ }
+
+ if (state->timecount < state->timelimit) {
+ te = tevent_add_timer(state->tctx->ev,
+ state,
+ timeval_current_ofs(1, 0),
+ test_smb2_bench_read_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_read_latency = total_read_latency / num_reads;
+
+ torture_comment(state->tctx,
+ "%.2f second: "
+ "read[num/s=%llu,bytes/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f] \r",
+ timeval_elapsed(&state->starttime),
+ (unsigned long long)num_reads,
+ (unsigned long long)num_reads*state->io_size,
+ avs_read_latency,
+ min_read_latency,
+ max_read_latency);
+ return;
+ }
+
+ avs_read_latency = state->total_latency / state->num_finished;
+ num_reads = state->num_finished / state->timelimit;
+
+ torture_comment(state->tctx,
+ "%.2f second: "
+ "read[num/s=%llu,bytes/s=%llu,avslat=%.6f,minlat=%.6f,maxlat=%.6f]\n",
+ timeval_elapsed(&state->starttime),
+ (unsigned long long)num_reads,
+ (unsigned long long)num_reads*state->io_size,
+ avs_read_latency,
+ state->min_latency,
+ state->max_latency);
+
+asserted:
+ state->stop = true;
+}
+
+static bool test_smb2_bench_read(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ struct test_smb2_bench_read_state *state = NULL;
+ bool ret = true;
+ int torture_nprocs = torture_setting_int(tctx, "nprocs", 4);
+ int torture_qdepth = torture_setting_int(tctx, "qdepth", 1);
+ int torture_io_size = torture_setting_int(tctx, "io_size", 4096);
+ size_t i;
+ size_t li = 0;
+ int looplimit = torture_setting_int(tctx, "looplimit", -1);
+ int timelimit = torture_setting_int(tctx, "timelimit", 10);
+ struct tevent_timer *te = NULL;
+ uint32_t timeout_msec;
+ const char *dname = "bench_read_dir";
+ const char *unique = generate_random_str(tctx, 8);
+ struct smb2_handle dh;
+ NTSTATUS status;
+
+ smb2_deltree(tree, dname);
+
+ status = torture_smb2_testdir(tree, dname, &dh);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ status = smb2_util_close(tree, dh);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ state = talloc_zero(tctx, struct test_smb2_bench_read_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_read_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_read_loop,
+ state->num_loops);
+ torture_assert(tctx, state->loops != NULL, __location__);
+ state->ok = true;
+ state->timelimit = MAX(timelimit, 1);
+ state->io_size = MAX(torture_io_size, 1);
+ state->io_size = MIN(state->io_size, 16*1024*1024);
+
+ 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_read_loop *loop = &state->loops[li];
+ struct smb2_create cr;
+ union smb_setfileinfo sfinfo;
+
+ loop->idx = li++;
+ if (looplimit != -1) {
+ loop->max_finished = looplimit;
+ } else {
+ loop->max_finished = UINT64_MAX;
+ }
+ loop->state = state;
+ loop->conn = &state->conns[i];
+ loop->im = tevent_create_immediate(state->loops);
+ torture_assert(tctx, loop->im != NULL, __location__);
+
+ loop->fname = talloc_asprintf(state->loops,
+ "%s\\%s_loop_%zu_conn_%zu_loop_%zu.dat",
+ dname, unique, li, i, pcli);
+ torture_assert(tctx, loop->fname != NULL, __location__);
+
+ /* reasonable default parameters */
+ ZERO_STRUCT(cr);
+ cr.in.create_flags = NTCREATEX_FLAGS_EXTENDED;
+ cr.in.alloc_size = state->io_size;
+ cr.in.desired_access = SEC_RIGHTS_FILE_ALL;
+ cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+ cr.in.share_access = NTCREATEX_SHARE_ACCESS_NONE;
+ cr.in.create_disposition = NTCREATEX_DISP_CREATE;
+ cr.in.create_options =
+ NTCREATEX_OPTIONS_DELETE_ON_CLOSE |
+ NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
+ cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+ cr.in.security_flags = 0;
+ cr.in.fname = loop->fname;
+ status = smb2_create(state->conns[i].tree, tctx, &cr);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ loop->handle = cr.out.file.handle;
+
+ ZERO_STRUCT(sfinfo);
+ sfinfo.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
+ sfinfo.end_of_file_info.in.file.handle = loop->handle;
+ sfinfo.end_of_file_info.in.size = state->io_size;
+ status = smb2_setinfo_file(state->conns[i].tree, &sfinfo);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ tevent_schedule_immediate(loop->im,
+ tctx->ev,
+ test_smb2_bench_read_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();
+ state->pending_loops = state->num_loops;
+
+ te = tevent_add_timer(tctx->ev,
+ state,
+ timeval_current_ofs(1, 0),
+ test_smb2_bench_read_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);
+ smb2_deltree(tree, dname);
+ return ret;
+}
+
+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, "echo", test_smb2_bench_echo);
+ torture_suite_add_1smb2_test(suite, "path-contention-shared", test_smb2_bench_path_contention_shared);
+ torture_suite_add_1smb2_test(suite, "read", test_smb2_bench_read);
+
+ suite->description = talloc_strdup(suite, "SMB2-BENCH tests");
+
+ return suite;
+}
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..175069d
--- /dev/null
+++ b/source4/torture/smb2/compound.c
@@ -0,0 +1,2595 @@
+/*
+ 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 "tevent.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)
+
+#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 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;
+ struct smb2_read r2;
+ 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, 3);
+
+ 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);
+
+ /*
+ * We send 2 reads in the compound here as the protocol
+ * allows the last read to be split off and possibly
+ * go async. Check the padding on the first read returned,
+ * not the second as the second may not be part of the
+ * returned compound.
+ */
+
+ 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);
+
+ ZERO_STRUCT(r2);
+ h.data[0] = UINT64_MAX;
+ h.data[1] = UINT64_MAX;
+ r2.in.file.handle = h;
+ r2.in.length = 3;
+ r2.in.offset = 0;
+ r2.in.min_count = 1;
+ req[2] = smb2_read_send(tree, &r2);
+
+ 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);
+
+ /* Pick up the second, possibly async, read. */
+ status = smb2_read_recv(req[2], tree, &r2);
+ 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, 3);
+
+ 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);
+
+ /*
+ * We send 2 reads in the compound here as the protocol
+ * allows the last read to be split off and possibly
+ * go async. Check the padding on the first read returned,
+ * not the second as the second may not be part of the
+ * returned compound.
+ */
+
+ 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);
+
+ ZERO_STRUCT(r2);
+ h.data[0] = UINT64_MAX;
+ h.data[1] = UINT64_MAX;
+ r2.in.file.handle = h;
+ r2.in.length = 3;
+ r2.in.offset = 0;
+ r2.in.min_count = 1;
+ req[2] = smb2_read_send(tree, &r2);
+
+ 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);
+
+ /* Pick up the second, possibly async, read. */
+ status = smb2_read_recv(req[2], tree, &r2);
+ 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 occasionally
+ * 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 occasionally
+ * 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;
+}
+
+/*
+ * For Samba/smbd this test must be run against the aio_delay_inject share
+ * as we need to ensure the last write in the compound takes longer than
+ * 500 us, which is the threshold for going async in smbd SMB2 writes.
+ */
+
+static bool test_compound_async_write_write(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_write w1;
+ struct smb2_write w2;
+ const char *fname = "compound_async_write_write";
+ struct smb2_request *req[2];
+ NTSTATUS status;
+ bool is_smbd = torture_setting_bool(tctx, "smbd", true);
+ 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 write + write handle. */
+ smb2_transport_compound_start(tree->session->transport, 2);
+
+ ZERO_STRUCT(w1);
+ w1.in.file.handle = fhandle;
+ w1.in.offset = 0;
+ w1.in.data = data_blob_talloc_zero(tctx, 64);
+ req[0] = smb2_write_send(tree, &w1);
+
+ torture_assert_not_null_goto(tctx, req[0], ret, done,
+ "smb2_write_send (1) failed\n");
+
+ smb2_transport_compound_set_related(tree->session->transport, true);
+
+ ZERO_STRUCT(w2);
+ w2.in.file.handle = relhandle;
+ w2.in.offset = 64;
+ w2.in.data = data_blob_talloc_zero(tctx, 64);
+ req[1] = smb2_write_send(tree, &w2);
+
+ torture_assert_not_null_goto(tctx, req[0], ret, done,
+ "smb2_write_send (2) failed\n");
+
+ status = smb2_write_recv(req[0], &w1);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_write_recv (1) failed.");
+
+ if (!is_smbd) {
+ /*
+ * Windows and other servers don't go async.
+ */
+ status = smb2_write_recv(req[1], &w2);
+ } else {
+ /*
+ * For smbd, the second write should go async
+ * as it's the last element of a compound.
+ */
+ WAIT_FOR_ASYNC_RESPONSE(req[1]);
+ CHECK_VALUE(req[1]->cancel.can_cancel, true);
+ /*
+ * Now pick up the real return.
+ */
+ status = smb2_write_recv(req[1], &w2);
+ }
+
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_write_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);
+
+ ret = true;
+
+ done:
+
+ if (fhandle.data[0] != 0) {
+ smb2_util_close(tree, fhandle);
+ }
+
+ smb2_util_unlink(tree, fname);
+ return ret;
+}
+
+/*
+ * For Samba/smbd this test must be run against the aio_delay_inject share
+ * as we need to ensure the last read in the compound takes longer than
+ * 500 us, which is the threshold for going async in smbd SMB2 reads.
+ */
+
+static bool test_compound_async_read_read(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_write w;
+ struct smb2_read r1;
+ struct smb2_read r2;
+ const char *fname = "compound_async_read_read";
+ struct smb2_request *req[2];
+ NTSTATUS status;
+ bool is_smbd = torture_setting_bool(tctx, "smbd", true);
+ 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);
+
+ /* Write 128 bytes. */
+ ZERO_STRUCT(w);
+ w.in.file.handle = fhandle;
+ w.in.offset = 0;
+ w.in.data = data_blob_talloc_zero(tctx, 128);
+ status = smb2_write(tree, &w);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_write_recv (1) failed.");
+
+ /* Now do a compound read + read handle. */
+ smb2_transport_compound_start(tree->session->transport, 2);
+
+ ZERO_STRUCT(r1);
+ r1.in.file.handle = fhandle;
+ r1.in.length = 64;
+ r1.in.offset = 0;
+ req[0] = smb2_read_send(tree, &r1);
+
+ torture_assert_not_null_goto(tctx, req[0], ret, done,
+ "smb2_read_send (1) failed\n");
+
+ smb2_transport_compound_set_related(tree->session->transport, true);
+
+ ZERO_STRUCT(r2);
+ r2.in.file.handle = relhandle;
+ r2.in.length = 64;
+ r2.in.offset = 64;
+ req[1] = smb2_read_send(tree, &r2);
+
+ torture_assert_not_null_goto(tctx, req[0], ret, done,
+ "smb2_read_send (2) failed\n");
+
+ status = smb2_read_recv(req[0], tree, &r1);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_read_recv (1) failed.");
+
+ if (!is_smbd) {
+ /*
+ * Windows and other servers don't go async.
+ */
+ status = smb2_read_recv(req[1], tree, &r2);
+ } else {
+ /*
+ * For smbd, the second write should go async
+ * as it's the last element of a compound.
+ */
+ WAIT_FOR_ASYNC_RESPONSE(req[1]);
+ CHECK_VALUE(req[1]->cancel.can_cancel, true);
+ /*
+ * Now pick up the real return.
+ */
+ status = smb2_read_recv(req[1], tree, &r2);
+ }
+
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_read_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);
+
+ 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);
+ torture_suite_add_1smb2_test(suite, "write_write",
+ test_compound_async_write_write);
+ torture_suite_add_1smb2_test(suite, "read_read",
+ test_compound_async_read_read);
+
+ 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..c1d132d
--- /dev/null
+++ b/source4/torture/smb2/create.c
@@ -0,0 +1,3629 @@
+/*
+ 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)
+
+/*
+ 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 cluster size and calculate 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);
+ 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;
+ uint32_t expected_access;
+ union smb_fileinfo getinfo;
+ union smb_setfileinfo setinfo;
+ struct security_descriptor *sd = NULL, *sd_orig = NULL;
+ const char *owner_sid = NULL;
+ struct create_disps_tests {
+ const char *file;
+ uint32_t create_disposition;
+ uint32_t create_options;
+ NTSTATUS expected_status;
+ };
+ struct create_disps_tests *cd_test = 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 greater 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);
+
+ expected_access = SEC_RIGHTS_FILE_ALL &
+ ~(SEC_FILE_EXECUTE | SEC_DIR_DELETE_CHILD);
+
+ torture_assert_int_equal_goto(tctx,
+ io.out.maximal_access & expected_access,
+ expected_access,
+ ret, done, "Bad access\n");
+
+ {
+ /*
+ * Test create dispositions
+ */
+ struct create_disps_tests cd_tests[] = {
+ {
+ .file = file,
+ .create_disposition = NTCREATEX_DISP_OPEN,
+ .expected_status = NT_STATUS_OK,
+ },
+ {
+ .file = file,
+ .create_disposition = NTCREATEX_DISP_OPEN_IF,
+ .expected_status = NT_STATUS_OK,
+ },
+ {
+ .file = file,
+ .create_disposition = NTCREATEX_DISP_OVERWRITE,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = file,
+ .create_disposition = NTCREATEX_DISP_OVERWRITE_IF,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = file,
+ .create_disposition = NTCREATEX_DISP_SUPERSEDE,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = "newfile",
+ .create_disposition = NTCREATEX_DISP_OPEN_IF,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = "newfile",
+ .create_disposition = NTCREATEX_DISP_OVERWRITE_IF,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = "newfile",
+ .create_disposition = NTCREATEX_DISP_CREATE,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = "newfile",
+ .create_disposition = NTCREATEX_DISP_SUPERSEDE,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = "newdir",
+ .create_disposition = NTCREATEX_DISP_OPEN_IF,
+ .create_options = NTCREATEX_OPTIONS_DIRECTORY,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = "newdir",
+ .create_disposition = NTCREATEX_DISP_CREATE,
+ .create_options = NTCREATEX_OPTIONS_DIRECTORY,
+ .expected_status = NT_STATUS_MEDIA_WRITE_PROTECTED,
+ },
+ {
+ .file = NULL,
+ },
+ };
+
+ for (cd_test = &cd_tests[0]; cd_test->file != NULL; cd_test++) {
+ io = (struct smb2_create) {
+ .in.fname = cd_test->file,
+ .in.create_disposition = cd_test->create_disposition,
+ .in.create_options = cd_test->create_options,
+
+ .in.desired_access = SEC_FILE_READ_DATA,
+ .in.file_attributes = FILE_ATTRIBUTE_NORMAL,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+ .in.timewarp = nttime,
+ };
+
+ status = smb2_create(tree, tctx, &io);
+ torture_assert_ntstatus_equal_goto(
+ tctx, status, cd_test->expected_status, ret, done,
+ "Bad status\n");
+ }
+ }
+
+ io = (struct smb2_create) {
+ .in.desired_access = expected_access,
+ .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");
+
+ /*
+ * Verify access mask
+ */
+
+ ZERO_STRUCT(getinfo);
+ getinfo.generic.level = RAW_FILEINFO_ACCESS_INFORMATION;
+ getinfo.generic.in.file.handle = h1;
+
+ status = smb2_getinfo_file(tree, tree, &getinfo);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_getinfo_file\n");
+
+ torture_assert_int_equal_goto(
+ tctx,
+ getinfo.access_information.out.access_flags,
+ expected_access,
+ ret, done,
+ "Bad access mask\n");
+
+ /*
+ * Check we can't set various things
+ */
+
+ ZERO_STRUCT(getinfo);
+ getinfo.query_secdesc.level = RAW_FILEINFO_SEC_DESC;
+ getinfo.query_secdesc.in.file.handle = h1;
+ getinfo.query_secdesc.in.secinfo_flags = SECINFO_DACL | SECINFO_OWNER;
+
+ status = smb2_getinfo_file(tree, tctx, &getinfo);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_getinfo_file\n");
+
+ sd_orig = getinfo.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_WRITE_DATA,
+ 0,
+ NULL);
+
+ /* Try to set ACL */
+
+ ZERO_STRUCT(setinfo);
+ setinfo.set_secdesc.level = RAW_SFILEINFO_SEC_DESC;
+ setinfo.set_secdesc.in.file.handle = h1;
+ setinfo.set_secdesc.in.secinfo_flags = SECINFO_DACL;
+ setinfo.set_secdesc.in.sd = sd;
+
+ status = smb2_setinfo_file(tree, &setinfo);
+ torture_assert_ntstatus_equal_goto(
+ tctx,
+ status,
+ NT_STATUS_MEDIA_WRITE_PROTECTED,
+ ret, done,
+ "smb2_setinfo_file\n");
+
+ /* Try to delete */
+
+ ZERO_STRUCT(setinfo);
+ setinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION;
+ setinfo.disposition_info.in.delete_on_close = 1;
+ setinfo.generic.in.file.handle = h1;
+
+ status = smb2_setinfo_file(tree, &setinfo);
+ torture_assert_ntstatus_equal_goto(
+ tctx,
+ status,
+ NT_STATUS_MEDIA_WRITE_PROTECTED,
+ ret, done,
+ "smb2_setinfo_file\n");
+
+ ZERO_STRUCT(setinfo);
+ setinfo.basic_info.in.attrib = FILE_ATTRIBUTE_HIDDEN;
+ setinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
+ setinfo.generic.in.file.handle = h1;
+
+ status = smb2_setinfo_file(tree, &setinfo);
+ torture_assert_ntstatus_equal_goto(
+ tctx,
+ status,
+ NT_STATUS_MEDIA_WRITE_PROTECTED,
+ ret, done,
+ "smb2_setinfo_file\n");
+
+ /* Try to truncate */
+
+ ZERO_STRUCT(setinfo);
+ setinfo.generic.level = SMB_SFILEINFO_END_OF_FILE_INFORMATION;
+ setinfo.generic.in.file.handle = h1;
+ setinfo.end_of_file_info.in.size = 0x100000;
+
+ status = smb2_setinfo_file(tree, &setinfo);
+ torture_assert_ntstatus_equal_goto(
+ tctx,
+ status,
+ NT_STATUS_MEDIA_WRITE_PROTECTED,
+ ret, done,
+ "smb2_setinfo_file\n");
+
+ /* Try to set a hardlink */
+
+ ZERO_STRUCT(setinfo);
+ setinfo.generic.level = RAW_SFILEINFO_LINK_INFORMATION;
+ setinfo.generic.in.file.handle = h1;
+ setinfo.link_information.in.new_name = "hardlink";
+
+ status = smb2_setinfo_file(tree, &setinfo);
+ torture_assert_ntstatus_equal_goto(
+ tctx,
+ status,
+ NT_STATUS_NOT_SAME_DEVICE,
+ ret, done,
+ "smb2_setinfo_file\n");
+
+ /* Try to rename */
+
+ ZERO_STRUCT(setinfo);
+ setinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
+ setinfo.rename_information.in.file.handle = h1;
+ setinfo.rename_information.in.new_name = "renamed";
+
+ status = smb2_setinfo_file(tree, &setinfo);
+ torture_assert_ntstatus_equal_goto(
+ tctx,
+ status,
+ NT_STATUS_NOT_SAME_DEVICE,
+ ret, done,
+ "smb2_setinfo_file\n");
+
+ smb2_util_close(tree, h1);
+ ZERO_STRUCT(h1);
+
+done:
+ if (!smb2_util_handle_empty(h1)) {
+ smb2_util_close(tree, h1);
+ }
+ 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 greater 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 greater 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 greater 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 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 = 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 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 = 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;
+}
+
+/**
+ Find Maximum Path Length
+ */
+static bool generate_path(const size_t len,
+ char *buffer,
+ const size_t buf_len)
+{
+ size_t i;
+
+ if (len >= buf_len) {
+ return false;
+ }
+
+ for (i = 0; i < len ; i++) {
+ buffer[i] = (char)(i % 10) + 48;
+ }
+ buffer[i] = '\0';
+ return true;
+}
+
+static bool test_path_length_test(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ const size_t max_name = 2048;
+ char *name = talloc_array(tctx, char, max_name);
+ struct smb2_handle fh = {{0}};
+ size_t length = 128;
+ size_t max_file_name = 0;
+ size_t max_path_length = 0;
+ char *path_ok = NULL;
+ char *path_next = NULL;
+ char *topdir = NULL;
+ bool is_interactive = torture_setting_bool(tctx, "interactive", false);
+ NTSTATUS status;
+ bool ret = true;
+
+ if (!is_interactive) {
+ torture_result(tctx, TORTURE_SKIP,
+ "Interactive Test: Skipping... "
+ "(enable with --interactive)\n");
+ return ret;
+ }
+
+ torture_comment(tctx, "Testing filename and path lengths\n");
+
+ /* Find Longest File Name */
+ for (length = 128; length < max_name; length++) {
+ if (!generate_path(length, name, max_name)) {
+ torture_result(tctx, TORTURE_FAIL,
+ "Failed to generate path.");
+ return false;
+ }
+
+ status = torture_smb2_testfile(tree, name, &fh);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ smb2_util_close(tree, fh);
+ smb2_util_unlink(tree, name);
+
+ max_file_name = length;
+ }
+
+ torture_assert_int_not_equal_goto(tctx, length, max_name, ret, done,
+ "Name too big\n");
+
+ torture_comment(tctx, "Max file name length: %zu\n", max_file_name);
+
+ /* Remove one char that caused the failure above */
+ name[max_file_name] = '\0';
+
+ path_ok = talloc_strdup(tree, name);
+ torture_assert_not_null_goto(tctx, path_ok, ret, done,
+ "talloc_strdup failed\n");
+
+ topdir = talloc_strdup(tree, name);
+ torture_assert_not_null_goto(tctx, topdir, ret, done,
+ "talloc_strdup failed\n");
+
+ status = smb2_util_mkdir(tree, path_ok);
+ if (!NT_STATUS_IS_OK(status)) {
+ torture_comment(tctx, "mkdir [%s] failed: %s\n",
+ path_ok, nt_errstr(status));
+ torture_result(tctx, TORTURE_FAIL, "Initial mkdir failed");
+ return false;
+ }
+
+ while (true) {
+ path_next = talloc_asprintf(tctx, "%s\\%s", path_ok, name);
+ torture_assert_not_null_goto(tctx, path_next, ret, done,
+ "talloc_asprintf failed\n");
+
+ status = smb2_util_mkdir(tree, path_next);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ path_ok = path_next;
+ }
+
+ for (length = 1; length < max_name; length++) {
+ if (!generate_path(length, name, max_name)) {
+ torture_result(tctx, TORTURE_FAIL,
+ "Failed to generate path.");
+ return false;
+ }
+
+ path_next = talloc_asprintf(tctx, "%s\\%s", path_ok, name);
+ torture_assert_not_null_goto(tctx, path_next, ret, done,
+ "talloc_asprintf failed\n");
+
+ status = torture_smb2_testfile(tree, path_next, &fh);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ smb2_util_close(tree, fh);
+ path_ok = path_next;
+ }
+
+ max_path_length = talloc_array_length(path_ok);
+
+ torture_comment(tctx, "Max path name length: %zu\n", max_path_length);
+
+done:
+ 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, "path-length", test_path_length_test);
+ 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;
+}
+
+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..4020e6b
--- /dev/null
+++ b/source4/torture/smb2/dir.c
@@ -0,0 +1,1606 @@
+/*
+ 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 = 1024*1024;
+ f.in.level = level;
+
+ /* The search should start from the beginning every time */
+ 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;
+ struct timespec ts1, ts2;
+
+ 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;
+
+ clock_gettime_mono(&ts1);
+
+ 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, "");
+
+ clock_gettime_mono(&ts2);
+
+ 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;
+ }
+
+ torture_comment(tctx, "Directory enumeration completed in %.3f s.\n",
+ timespec_elapsed2(&ts1, &ts2));
+
+done:
+ smb2_util_close(tree, h);
+ smb2_deltree(tree, DNAME);
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/*
+ * basic testing of search calls using many files,
+ * including renaming files
+ */
+#define NUM_FILES 1000
+static bool test_1k_files_rename(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ const int num_files = NUM_FILES;
+ int rename_index = 0;
+ int i, j;
+ char *fname_list[NUM_FILES] = {0};
+ bool ret = true;
+ NTSTATUS status;
+ struct multiple_result result;
+ struct smb2_create create;
+ struct smb2_create dir;
+ struct smb2_handle dir_handle;
+ struct smb2_create open;
+ union smb_setfileinfo sinfo;
+ struct timespec ts1, ts2;
+ bool reopen;
+
+ reopen = torture_setting_bool(tctx, "1k_files_rename_reopendir", false);
+
+ torture_comment(tctx, "Testing with %d files\n", num_files);
+
+ smb2_deltree(tree, DNAME);
+
+ dir = (struct smb2_create) {
+ .in.desired_access = SEC_DIR_LIST,
+ .in.file_attributes = FILE_ATTRIBUTE_DIRECTORY,
+ .in.create_disposition = NTCREATEX_DISP_OPEN_IF,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+ .in.create_options = NTCREATEX_OPTIONS_DIRECTORY,
+ .in.fname = DNAME,
+ };
+
+ status = smb2_create(tree, tree, &dir);
+ torture_assert_ntstatus_ok(tctx, status,
+ "Could not create test directory");
+
+ dir_handle = dir.out.file.handle;
+
+ /* Create 1k files, store in array for later rename */
+
+ torture_comment(tctx, "Create files.\n");
+ create = (struct smb2_create) {
+ .in.desired_access = SEC_RIGHTS_FILE_ALL,
+ .in.file_attributes = FILE_ATTRIBUTE_NORMAL,
+ .in.create_disposition = NTCREATEX_DISP_CREATE,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+ };
+
+ for (i = num_files - 1; i >= 0; i--) {
+ fname_list[i] = talloc_asprintf(mem_ctx, DNAME "\\t%03d.txt", i);
+ torture_assert_not_null_goto(tctx, fname_list[i], ret, done,
+ "talloc_asprintf failed");
+
+ create.in.fname = fname_list[i];
+
+ status = smb2_create(tree, mem_ctx, &create);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_create failed\n");
+
+ status = smb2_util_close(tree, create.out.file.handle);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_util_close failed\n");
+ }
+
+ open = (struct smb2_create) {
+ .in.desired_access = SEC_RIGHTS_FILE_ALL | SEC_STD_DELETE,
+ .in.file_attributes = FILE_ATTRIBUTE_NORMAL,
+ .in.create_disposition = NTCREATEX_DISP_OPEN,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+ };
+
+ clock_gettime_mono(&ts1);
+
+ for (j = 0; j < 100; j++) {
+ ZERO_STRUCT(result);
+ result.tctx = talloc_new(tctx);
+
+ torture_comment(tctx, "Iteration: %02d of 100.\n", j);
+
+ if (reopen) {
+ torture_comment(
+ tctx, "Close and reopen test directory \n");
+ smb2_util_close(tree, dir_handle);
+
+ status = smb2_create(tree, tree, &dir);
+ torture_assert_ntstatus_ok_goto(
+ tctx, status, ret, done,
+ "Could not reopen test directory");
+
+ dir_handle = dir.out.file.handle;
+ }
+
+ status = multiple_smb2_search(tree, tctx, "*",
+ SMB2_FIND_FULL_DIRECTORY_INFO,
+ RAW_SEARCH_DATA_FULL_DIRECTORY_INFO,
+ 100,
+ &result, &dir_handle);
+ torture_assert_int_equal_goto(tctx, result.count, num_files,
+ ret, done, "Wrong number of files");
+
+ /* Check name validity of all files*/
+ compare_data_level = RAW_SEARCH_DATA_FULL_DIRECTORY_INFO;
+ level_sort = SMB2_FIND_FULL_DIRECTORY_INFO;
+
+ TYPESAFE_QSORT(result.list, result.count, search_compare);
+
+ for (i = 0; i < result.count; i++) {
+ const char *s = NULL;
+ char *dname_s = NULL;
+
+ s = extract_name(&result.list[i],
+ SMB2_FIND_FULL_DIRECTORY_INFO,
+ RAW_SEARCH_DATA_FULL_DIRECTORY_INFO);
+ dname_s = talloc_asprintf(mem_ctx, DNAME "\\%s", s);
+ torture_assert_not_null_goto(tctx, dname_s, ret, done,
+ "talloc_asprintf failed");
+
+ torture_assert_str_equal_goto(tctx, dname_s, fname_list[i], ret,
+ done, "Incorrect name\n");
+ TALLOC_FREE(dname_s);
+ }
+
+ TALLOC_FREE(result.tctx);
+
+ /* Rename one file */
+ open.in.fname = fname_list[rename_index];
+
+ status = smb2_create(tree, tctx, &open);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "Failed open\n");
+
+ sinfo = (union smb_setfileinfo) {
+ .rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION,
+ .rename_information.in.file.handle = open.out.file.handle,
+ };
+
+ TALLOC_FREE(fname_list[rename_index]);
+ fname_list[rename_index] = talloc_asprintf(
+ mem_ctx, DNAME "\\t%03d-rename.txt", rename_index);
+ sinfo.rename_information.in.new_name = fname_list[rename_index];
+
+ torture_comment(tctx, "Renaming test file to: %s\n",
+ sinfo.rename_information.in.new_name);
+
+ status = smb2_setinfo_file(tree, &sinfo);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "Failed setinfo/rename\n");
+
+ rename_index++;
+ smb2_util_close(tree, open.out.file.handle);
+ }
+
+ clock_gettime_mono(&ts2);
+
+ torture_comment(tctx, "\nDirectory enumeration completed in %.3f s.\n",
+ timespec_elapsed2(&ts1, &ts2));
+
+done:
+ TALLOC_FREE(mem_ctx);
+ smb2_util_close(tree, dir_handle);
+ smb2_deltree(tree, DNAME);
+ return ret;
+}
+#undef NUM_FILES
+
+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);
+ torture_suite_add_1smb2_test(suite, "1kfiles_rename", test_1k_files_rename);
+
+ 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..f56c558
--- /dev/null
+++ b/source4/torture/smb2/durable_open.c
@@ -0,0 +1,2872 @@
+/*
+ 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); \
+ if (__alloc_size != 0) { \
+ 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.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..987d90d
--- /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 something 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 fail\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..3765dc0
--- /dev/null
+++ b/source4/torture/smb2/ioctl.c
@@ -0,0 +1,7552 @@
+/*
+ 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
+ * specific 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_SET_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 consistent */
+ 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..30bbefd
--- /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 resulting 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, &timesup);
+ 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..09097ee
--- /dev/null
+++ b/source4/torture/smb2/mangle.c
@@ -0,0 +1,341 @@
+/*
+ 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;
+}
+
+
+static 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);
+}
+
+static bool test_mangled_mask(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ bool ret = true;
+ const char *dname = "single_find_with_mangled_name";
+ const char *fname = "single_find_with_mangled_name\\verylongfilename";
+ const char *shortname = NULL;
+ NTSTATUS status;
+ struct smb2_handle dh = {{0}};
+ struct smb2_handle fh = {{0}};
+ struct smb2_find f;
+ union smb_search_data *d;
+ unsigned count, i;
+
+ torture_comment(tctx, "Checking find with mangled search mask\n");
+
+ smb2_deltree(tree, dname);
+
+ status = torture_smb2_testdir(tree, dname, &dh);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "torture_smb2_testdir failed");
+
+ status = torture_smb2_testfile(tree, fname, &fh);
+ smb2_util_close(tree, fh);
+
+ ZERO_STRUCT(f);
+ f.in.file.handle = dh;
+ f.in.pattern = "*";
+ 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_find_level failed\n");
+ smb2_util_close(tree, dh);
+ ZERO_STRUCT(dh);
+
+ for (i = 0; i < count; i++) {
+ const char *found = d[i].both_directory_info.name.s;
+
+ if (!strcmp(found, ".") || !strcmp(found, "..")) {
+ continue;
+ }
+
+ torture_assert_str_equal_goto(tctx, found, "verylongfilename", ret, done,
+ "Bad filename\n");
+ shortname = d[i].both_directory_info.short_name.s;
+ break;
+ }
+
+ torture_assert_not_null_goto(tctx, shortname, ret, done,
+ "shortname is NULL\n");
+
+ torture_comment(tctx, "Got shortname: %s\n", shortname);
+
+ status = torture_smb2_testdir(tree, dname, &dh);
+
+ ZERO_STRUCT(f);
+ f.in.file.handle = dh;
+ f.in.continue_flags = SMB2_CONTINUE_FLAG_SINGLE;
+ f.in.pattern = shortname;
+ 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_find_level failed\n");
+ smb2_util_close(tree, dh);
+ ZERO_STRUCT(dh);
+
+done:
+ if (!smb2_util_handle_empty(fh)) {
+ smb2_util_close(tree, fh);
+ }
+ if (!smb2_util_handle_empty(dh)) {
+ smb2_util_close(tree, dh);
+ }
+ smb2_deltree(tree, dname);
+ return ret;
+
+}
+
+struct torture_suite *torture_smb2_name_mangling_init(TALLOC_CTX *ctx)
+{
+ struct torture_suite *suite = NULL;
+
+ suite = torture_suite_create(ctx, "name-mangling");
+ suite->description = talloc_strdup(suite, "SMB2 name mangling tests");
+
+ torture_suite_add_1smb2_test(suite, "mangle", torture_smb2_mangle);
+ torture_suite_add_1smb2_test(suite, "mangled-mask", test_mangled_mask);
+ return suite;
+}
diff --git a/source4/torture/smb2/max_allowed.c b/source4/torture/smb2/max_allowed.c
new file mode 100644
index 0000000..af8b08a
--- /dev/null
+++ b/source4/torture/smb2/max_allowed.c
@@ -0,0 +1,248 @@
+/*
+ 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"
+static 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;
+}
+
+static bool torture_smb2_read_only_file(struct torture_context *tctx,
+ struct smb2_tree *tree)
+{
+ struct smb2_create c;
+ struct smb2_handle h = {{0}};
+ bool ret = true;
+ NTSTATUS status;
+
+ smb2_deltree(tree, MAXIMUM_ALLOWED_FILE);
+
+ c = (struct smb2_create) {
+ .in.desired_access = SEC_RIGHTS_FILE_ALL,
+ .in.file_attributes = FILE_ATTRIBUTE_READONLY,
+ .in.create_disposition = NTCREATEX_DISP_CREATE,
+ .in.fname = MAXIMUM_ALLOWED_FILE,
+ };
+
+ status = smb2_create(tree, tctx, &c);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_create failed\n");
+ h = c.out.file.handle;
+ smb2_util_close(tree, h);
+ ZERO_STRUCT(h);
+
+ c = (struct smb2_create) {
+ .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED,
+ .in.file_attributes = FILE_ATTRIBUTE_READONLY,
+ .in.create_disposition = NTCREATEX_DISP_OPEN,
+ .in.fname = MAXIMUM_ALLOWED_FILE,
+ };
+
+ status = smb2_create(tree, tctx, &c);
+ torture_assert_ntstatus_ok_goto(
+ tctx, status, ret, done,
+ "Failed to open READ-ONLY file with SEC_FLAG_MAXIMUM_ALLOWED\n");
+ h = c.out.file.handle;
+ smb2_util_close(tree, h);
+ ZERO_STRUCT(h);
+
+done:
+ if (!smb2_util_handle_empty(h)) {
+ smb2_util_close(tree, h);
+ }
+ smb2_deltree(tree, MAXIMUM_ALLOWED_FILE);
+ return ret;
+}
+
+struct torture_suite *torture_smb2_max_allowed(TALLOC_CTX *ctx)
+{
+ struct torture_suite *suite = torture_suite_create(ctx, "maximum_allowed");
+
+ torture_suite_add_1smb2_test(suite, "maximum_allowed", torture_smb2_maximum_allowed);
+ torture_suite_add_1smb2_test(suite, "read_only", torture_smb2_read_only_file);
+ return suite;
+}
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..6b6e00e
--- /dev/null
+++ b/source4/torture/smb2/multichannel.c
@@ -0,0 +1,2743 @@
+/*
+ * 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.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;
+
+ 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, mem_ctx, ne, timeout_cb, &timesup);
+ 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;
+ struct timeval current_time;
+ struct tm tm_buf;
+ struct tm *current_tm = NULL;
+ char time_str[sizeof "10000-01-01T00:00:00"];
+ size_t time_str_len;
+ 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");
+
+ current_time = tevent_timeval_current();
+ current_tm = gmtime_r(&current_time.tv_sec, &tm_buf);
+ torture_assert_not_null_goto(tctx, current_tm, ok, asserted,
+ "gmtime_r failed");
+
+ time_str_len = strftime(time_str, sizeof time_str, "%FT%T", current_tm);
+ torture_assert_size_not_equal_goto(tctx, time_str_len, 0, ok, asserted,
+ "strftime failed");
+
+ torture_comment(tctx,
+ "%s.%ldZ: conn[%zu]: negprot done\n",
+ time_str,
+ (long)current_time.tv_usec,
+ 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;
+ struct timeval current_time;
+ struct tm tm_buf;
+ struct tm *current_tm = NULL;
+ char time_str[sizeof "10000-01-01T00:00:00"];
+ size_t time_str_len;
+ const char *outcome = NULL;
+ NTSTATUS status;
+ bool ok = false;
+
+ SMB_ASSERT(conn->ereq == subreq);
+ conn->ereq = NULL;
+
+ current_time = tevent_timeval_current();
+ current_tm = gmtime_r(&current_time.tv_sec, &tm_buf);
+ torture_assert_not_null_goto(tctx, current_tm, ok, asserted,
+ "gmtime_r failed");
+
+ time_str_len = strftime(time_str, sizeof time_str, "%FT%T", current_tm);
+ torture_assert_size_not_equal_goto(tctx, time_str_len, 0, ok, asserted,
+ "strftime failed");
+
+ status = smb2cli_echo_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ outcome = "timed out";
+ } else if (!NT_STATUS_IS_OK(status)) {
+ outcome = "failed";
+ } else {
+ outcome = "done";
+ }
+ torture_comment(tctx,
+ "%s.%ldZ: conn[%zu]: echo %s\n",
+ time_str,
+ (long)current_time.tv_usec,
+ conn->idx,
+ outcome);
+ torture_assert_ntstatus_ok_goto(tctx, status, ok, asserted,
+ "smb2cli_echo_recv failed");
+
+ 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 the additional 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;
+ }
+
+ /*
+ * Now 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");
+
+ /*
+ * Now 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, &notify);
+ 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, &notify);
+ 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, &notify);
+ torture_assert_not_null_goto(torture, req, ok, done, "smb2_notify_send failed\n");
+
+ status = smb2_notify_recv(req, torture, &notify);
+ 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, &notify.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, &notify.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..90bf4d2
--- /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 disposition 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 disposition 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 disposition 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 disposition 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, &timesup);
+ 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..12636c4
--- /dev/null
+++ b/source4/torture/smb2/rename.c
@@ -0,0 +1,1751 @@
+/*
+ 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/util.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;
+}
+
+/*
+ * This test basically verifies that modify and change timestamps are preserved
+ * after file rename with outstanding open file handles.
+ */
+
+static bool torture_smb2_rename_simple_modtime(
+ struct torture_context *torture,
+ struct smb2_tree *tree1)
+{
+ struct smb2_create c1, c2;
+ union smb_fileinfo gi;
+ union smb_setfileinfo si;
+ struct smb2_handle h1 = {{0}};
+ struct smb2_handle h2 = {{0}};
+ NTSTATUS status;
+ bool ret = true;
+
+ smb2_deltree(tree1, BASEDIR);
+ smb2_util_mkdir(tree1, BASEDIR);
+
+ torture_comment(torture, "Creating test file: file1.txt\n");
+
+ c1 = (struct smb2_create) {
+ .in.desired_access = SEC_FILE_ALL|SEC_STD_DELETE,
+ .in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE,
+ .in.file_attributes = FILE_ATTRIBUTE_NORMAL,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+ .in.create_disposition = NTCREATEX_DISP_CREATE,
+ .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS,
+ .in.fname = BASEDIR "\\file1.txt",
+ };
+
+ status = smb2_create(tree1, torture, &c1);
+ torture_assert_ntstatus_ok_goto(torture, status, ret, done,
+ "smb2_create failed\n");
+ h1 = c1.out.file.handle;
+
+ torture_comment(torture, "Waitig for 5 secs..\n");
+ sleep(5);
+
+ torture_comment(torture, "Creating test file: file2.txt\n");
+
+ c2 = (struct smb2_create) {
+ .in.desired_access = SEC_FILE_ALL|SEC_STD_DELETE,
+ .in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE,
+ .in.file_attributes = FILE_ATTRIBUTE_NORMAL,
+ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
+ .in.create_disposition = NTCREATEX_DISP_CREATE,
+ .in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS,
+ .in.fname = BASEDIR "\\file2.txt",
+ };
+
+ status = smb2_create(tree1, torture, &c2);
+ torture_assert_ntstatus_ok_goto(torture, status, ret, done,
+ "smb2_create failed\n");
+ h2 = c2.out.file.handle;
+
+ torture_comment(torture, "Renaming file1.txt --> tmp1.txt\n");
+
+ si = (union smb_setfileinfo) {
+ .rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION,
+ .rename_information.in.file.handle = h1,
+ .rename_information.in.new_name =
+ BASEDIR "\\tmp1.txt",
+ };
+
+ status = smb2_setinfo_file(tree1, &si);
+ torture_assert_ntstatus_ok_goto(torture, status, ret, done,
+ "smb2_setinfo_file failed\n");
+
+ torture_comment(torture, "GetInfo of tmp1.txt\n");
+
+ gi = (union smb_fileinfo) {
+ .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION,
+ .generic.in.file.handle = h1,
+ };
+
+ status = smb2_getinfo_file(tree1, torture, &gi);
+ torture_assert_ntstatus_ok_goto(torture, status, ret, done,
+ "smb2_getinfo_file failed\n");
+
+ torture_comment(torture, "Check if timestamps are good after rename(file1.txt --> tmp1.txt).\n");
+
+ torture_assert_nttime_equal(
+ torture, c1.out.write_time, gi.all_info.out.write_time,
+ "Bad timestamp\n");
+ torture_assert_nttime_equal(
+ torture, c1.out.change_time, gi.all_info.out.change_time,
+ "Bad timestamp\n");
+
+ torture_comment(torture, "Renaming file2.txt --> file1.txt\n");
+
+ si = (union smb_setfileinfo) {
+ .rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION,
+ .rename_information.in.file.handle = h2,
+ .rename_information.in.new_name =
+ BASEDIR "\\file1.txt",
+ };
+ status = smb2_setinfo_file(tree1, &si);
+ torture_assert_ntstatus_ok_goto(torture, status, ret, done,
+ "smb2_setinfo_file failed\n");
+
+ torture_comment(torture, "GetInfo of file1.txt\n");
+
+ gi = (union smb_fileinfo) {
+ .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION,
+ .generic.in.file.handle = h2,
+ };
+
+ status = smb2_getinfo_file(tree1, torture, &gi);
+ torture_assert_ntstatus_ok_goto(torture, status, ret, done,
+ "smb2_getinfo_file failed\n");
+
+ torture_comment(torture, "Check if timestamps are good after rename(file2.txt --> file1.txt).\n");
+
+ torture_assert_nttime_equal(
+ torture, c2.out.write_time, gi.all_info.out.write_time,
+ "Bad timestamp\n");
+ torture_assert_nttime_equal(
+ torture, c2.out.change_time, gi.all_info.out.change_time,
+ "Bad timestamp\n");
+
+done:
+ if (!smb2_util_handle_empty(h1)) {
+ smb2_util_close(tree1, h1);
+ }
+ if (!smb2_util_handle_empty(h2)) {
+ smb2_util_close(tree1, h2);
+ }
+ smb2_deltree(tree1, BASEDIR);
+ return ret;
+}
+
+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_modtime",
+ torture_smb2_rename_simple_modtime);
+
+ 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..d84ced8
--- /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 expected 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 expects 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 expects 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, &notify);
+
+ 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, &notify);
+ 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..6696c06
--- /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 extensions).
+ *
+ * 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..823304f
--- /dev/null
+++ b/source4/torture/smb2/session.c
@@ -0,0 +1,5670 @@
+/*
+ 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"
+
+/* Ticket lifetime we want to request in seconds */
+#define KRB5_TICKET_LIFETIME 5
+/* Allowed clock skew in seconds */
+#define KRB5_CLOCKSKEW 5
+/* Time till ticket fully expired in seconds */
+#define KRB5_TICKET_EXPIRETIME KRB5_TICKET_LIFETIME + KRB5_CLOCKSKEW
+
+#define texpand(x) #x
+#define GENSEC_GSSAPI_REQUESTED_LIFETIME(x) \
+ "gensec_gssapi:requested_life_time=" texpand(x)
+
+#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; \
+ } \
+ }
+
+static void sleep_remaining(struct torture_context *tctx,
+ const struct timeval *endtime)
+{
+ struct timeval current = tevent_timeval_current();
+ double remaining_secs = timeval_elapsed2(&current, endtime);
+
+ remaining_secs = remaining_secs < 1.0 ? 1.0 : remaining_secs;
+ torture_comment(
+ tctx,
+ "sleep for %2.f second(s) that the krb5 ticket expires",
+ remaining_secs);
+ smb_msleep((int)(remaining_secs * 1000));
+}
+
+/**
+ * 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;
+ struct timeval endtime;
+ bool ticket_expired = false;
+
+ use_kerberos = cli_credentials_get_kerberos_state(credentials);
+ if (use_kerberos != CRED_USE_KERBEROS_REQUIRED) {
+ torture_warning(tctx,
+ "smb2.session.expire1 requires "
+ "--use-kerberos=required!");
+ torture_skip(tctx,
+ "smb2.session.expire1 requires "
+ "--use-kerberos=required!");
+ }
+
+ torture_assert_int_equal(tctx,
+ use_kerberos,
+ CRED_USE_KERBEROS_REQUIRED,
+ "please use --use-kerberos=required");
+
+ cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED);
+
+ lpcfg_set_option(
+ tctx->lp_ctx,
+ GENSEC_GSSAPI_REQUESTED_LIFETIME(KRB5_TICKET_LIFETIME));
+
+ 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)
+ );
+ /*
+ * We request a ticket lifetime of KRB5_TICKET_LIFETIME seconds.
+ * Give the server at least KRB5_TICKET_LIFETIME + KRB5_CLOCKSKEW + a
+ * few more milliseconds for this to kick in.
+ */
+ endtime = timeval_current_ofs(KRB5_TICKET_EXPIRETIME, 500 * 1000);
+ 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, "%s: query info => OK\n",
+ current_timestring(tctx, true));
+
+ ZERO_STRUCT(qfinfo.access_information.out);
+ status = smb2_getinfo_file(tree, tctx, &qfinfo);
+ torture_comment(tctx, "%s: %s:%s: after smb2_getinfo_file() => %s\n",
+ current_timestring(tctx, true),
+ __location__, __func__,
+ nt_errstr(status));
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_getinfo_file failed");
+
+ sleep_remaining(tctx, &endtime);
+
+ torture_comment(tctx, "%s: query info => EXPIRED\n",
+ current_timestring(tctx, true));
+ ZERO_STRUCT(qfinfo.access_information.out);
+ status = smb2_getinfo_file(tree, tctx, &qfinfo);
+ torture_comment(tctx, "%s: %s:%s: after smb2_getinfo_file() => %s\n",
+ current_timestring(tctx, true),
+ __location__, __func__,
+ nt_errstr(status));
+ 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, "%s: reauth => OK\n",
+ current_timestring(tctx, true));
+ status = smb2_session_setup_spnego(tree->session,
+ credentials,
+ 0 /* previous_session_id */);
+ /*
+ * We request a ticket lifetime of KRB5_TICKET_LIFETIME seconds.
+ * Give the server at least KRB5_TICKET_LIFETIME +
+ * KRB5_CLOCKSKEW + a few more milliseconds for this to kick in.
+ */
+ endtime = timeval_current_ofs(KRB5_TICKET_EXPIRETIME,
+ 500 * 1000);
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_session_setup_spnego failed");
+
+ smb2cli_session_require_signed_response(
+ tree->session->smbXcli, false);
+ }
+
+ ticket_expired = timeval_expired(&endtime);
+ if (ticket_expired) {
+ struct timeval current = timeval_current();
+ double remaining_secs = timeval_elapsed2(&current, &endtime);
+ remaining_secs = remaining_secs < 0.0 ? remaining_secs * -1.0
+ : remaining_secs;
+ torture_warning(
+ tctx,
+ "The ticket already expired %.2f seconds ago. "
+ "You might want to increase KRB5_TICKET_LIFETIME.",
+ remaining_secs);
+ }
+ torture_assert(tctx,
+ ticket_expired == false,
+ "The kerberos ticket already expired");
+ ZERO_STRUCT(qfinfo.access_information.out);
+ torture_comment(tctx, "%s: %s:%s: before smb2_getinfo_file()\n",
+ current_timestring(tctx, true),
+ __location__, __func__);
+ status = smb2_getinfo_file(tree, tctx, &qfinfo);
+ torture_comment(tctx, "%s: %s:%s: after smb2_getinfo_file() => %s\n",
+ current_timestring(tctx, true),
+ __location__, __func__,
+ nt_errstr(status));
+ 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_LIFETIME(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;
+ struct timeval endtime;
+
+ use_kerberos = cli_credentials_get_kerberos_state(credentials);
+ if (use_kerberos != CRED_USE_KERBEROS_REQUIRED) {
+ torture_warning(tctx,
+ "smb2.session.expire1 requires "
+ "--use-kerberos=required!");
+ torture_skip(tctx,
+ "smb2.session.expire1 requires "
+ "--use-kerberos=required!");
+ }
+
+ torture_assert_int_equal(tctx,
+ use_kerberos,
+ CRED_USE_KERBEROS_REQUIRED,
+ "please use --use-kerberos=required");
+
+ cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED);
+
+ lpcfg_set_option(
+ tctx->lp_ctx,
+ GENSEC_GSSAPI_REQUESTED_LIFETIME(KRB5_TICKET_LIFETIME));
+
+ 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)
+ );
+ /*
+ * We request a ticket lifetime of KRB5_TICKET_LIFETIME seconds.
+ * Give the server at least KRB5_TICKET_LIFETIME + KRB5_CLOCKSKEW + a
+ * few more milliseconds for this to kick in.
+ */
+ endtime = timeval_current_ofs(KRB5_TICKET_EXPIRETIME, 500 * 1000);
+ 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 = &el;
+ 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");
+
+ sleep_remaining(tctx, &endtime);
+
+ 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_LIFETIME(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;
+ struct timeval endtime;
+
+ use_kerberos = cli_credentials_get_kerberos_state(credentials);
+ if (use_kerberos != CRED_USE_KERBEROS_REQUIRED) {
+ torture_warning(tctx,
+ "smb2.session.expire1 requires "
+ "--use-kerberos=required!");
+ torture_skip(tctx,
+ "smb2.session.expire1 requires "
+ "--use-kerberos=required!");
+ }
+
+ cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED);
+
+ lpcfg_set_option(
+ tctx->lp_ctx,
+ GENSEC_GSSAPI_REQUESTED_LIFETIME(KRB5_TICKET_LIFETIME));
+ 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)
+ );
+ /*
+ * We request a ticket lifetime of KRB5_TICKET_LIFETIME seconds.
+ * Give the server at least KRB5_TICKET_LIFETIME + KRB5_CLOCKSKEW + a
+ * few more milliseconds for this to kick in.
+ */
+ endtime = timeval_current_ofs(KRB5_TICKET_EXPIRETIME, 500 * 1000);
+ 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");
+
+ sleep_remaining(tctx, &endtime);
+
+ 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_LIFETIME(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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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 encryption 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..5b6477e
--- /dev/null
+++ b/source4/torture/smb2/smb2.c
@@ -0,0 +1,230 @@
+/*
+ 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, "winattr2", torture_smb2_winattr2);
+ torture_suite_add_1smb2_test(suite, "sdread", torture_smb2_sdreadtest);
+ torture_suite_add_suite(suite, torture_smb2_readwrite_init(suite));
+ torture_suite_add_suite(suite, torture_smb2_max_allowed(suite));
+ 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_name_mangling_init(suite));
+
+ 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..3d6d3d1
--- /dev/null
+++ b/source4/torture/smb2/timestamps.c
@@ -0,0 +1,1344 @@
+/*
+ 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.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;
+ 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);
+
+ torture_comment(tctx, "Check writetime has been 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);
+ close_time = c.out.write_time;
+
+ if (!(close_time > write_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 updates
+ */
+ 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..533639c
--- /dev/null
+++ b/source4/torture/smb2/wscript_build
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+bld.SAMBA_MODULE('TORTURE_SMB2',
+ source='''
+ acls.c
+ attr.c
+ block.c
+ bench.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'
+ )
+