diff options
Diffstat (limited to 'source4/torture/smb2/compound.c')
-rw-r--r-- | source4/torture/smb2/compound.c | 2341 |
1 files changed, 2341 insertions, 0 deletions
diff --git a/source4/torture/smb2/compound.c b/source4/torture/smb2/compound.c new file mode 100644 index 0000000..47a550f --- /dev/null +++ b/source4/torture/smb2/compound.c @@ -0,0 +1,2341 @@ +/* + Unix SMB/CIFS implementation. + + test suite for SMB2 compounded requests + + Copyright (C) Stefan Metzmacher 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "../libcli/smb/smbXcli_base.h" + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ + nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +#define CHECK_VALUE(v, correct) do { \ + if ((v) != (correct)) { \ + torture_result(tctx, TORTURE_FAIL, \ + "(%s) Incorrect value %s=%d - should be %d\n", \ + __location__, #v, (int)v, (int)correct); \ + ret = false; \ + }} while (0) + +static struct { + struct smb2_handle handle; + uint8_t level; + struct smb2_break br; + int count; + int failures; + NTSTATUS failure_status; +} break_info; + +static void torture_oplock_break_callback(struct smb2_request *req) +{ + NTSTATUS status; + struct smb2_break br; + + ZERO_STRUCT(br); + status = smb2_break_recv(req, &break_info.br); + if (!NT_STATUS_IS_OK(status)) { + break_info.failures++; + break_info.failure_status = status; + } + + return; +} + +/* A general oplock break notification handler. This should be used when a + * test expects to break from batch or exclusive to a lower level. */ +static bool torture_oplock_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, + void *private_data) +{ + struct smb2_tree *tree = private_data; + const char *name; + struct smb2_request *req; + ZERO_STRUCT(break_info.br); + + break_info.handle = *handle; + break_info.level = level; + break_info.count++; + + switch (level) { + case SMB2_OPLOCK_LEVEL_II: + name = "level II"; + break; + case SMB2_OPLOCK_LEVEL_NONE: + name = "none"; + break; + default: + name = "unknown"; + break_info.failures++; + } + printf("Acking to %s [0x%02X] in oplock handler\n", name, level); + + break_info.br.in.file.handle = *handle; + break_info.br.in.oplock_level = level; + break_info.br.in.reserved = 0; + break_info.br.in.reserved2 = 0; + + req = smb2_break_send(tree, &break_info.br); + req->async.fn = torture_oplock_break_callback; + req->async.private_data = NULL; + return true; +} + +static bool test_compound_break(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname1 = "some-file.pptx"; + NTSTATUS status; + bool ret = true; + union smb_open io1; + struct smb2_create io2; + struct smb2_getinfo gf; + struct smb2_request *req[2]; + struct smb2_handle h1; + struct smb2_handle h; + + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + ZERO_STRUCT(break_info); + + /* + base ntcreatex parms + */ + ZERO_STRUCT(io1.smb2); + io1.generic.level = RAW_OPEN_SMB2; + io1.smb2.in.desired_access = (SEC_STD_SYNCHRONIZE| + SEC_STD_READ_CONTROL| + SEC_FILE_READ_ATTRIBUTE| + SEC_FILE_READ_EA| + SEC_FILE_READ_DATA); + io1.smb2.in.alloc_size = 0; + io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io1.smb2.in.create_options = 0; + io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io1.smb2.in.security_flags = 0; + io1.smb2.in.fname = fname1; + + torture_comment(tctx, "TEST2: open a file with an batch " + "oplock (share mode: all)\n"); + io1.smb2.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + + status = smb2_create(tree, tctx, &(io1.smb2)); + torture_assert_ntstatus_ok(tctx, status, "Error opening the file"); + + h1 = io1.smb2.out.file.handle; + + torture_comment(tctx, "TEST2: Opening second time with compound\n"); + + ZERO_STRUCT(io2); + + io2.in.desired_access = (SEC_STD_SYNCHRONIZE| + SEC_FILE_READ_ATTRIBUTE| + SEC_FILE_READ_EA); + io2.in.alloc_size = 0; + io2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io2.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + io2.in.create_disposition = NTCREATEX_DISP_OPEN; + io2.in.create_options = 0; + io2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + io2.in.security_flags = 0; + io2.in.fname = fname1; + io2.in.oplock_level = 0; + + smb2_transport_compound_start(tree->session->transport, 2); + + req[0] = smb2_create_send(tree, &io2); + + smb2_transport_compound_set_related(tree->session->transport, true); + + h.data[0] = UINT64_MAX; + h.data[1] = UINT64_MAX; + + ZERO_STRUCT(gf); + gf.in.file.handle = h; + gf.in.info_type = SMB2_0_INFO_FILE; + gf.in.info_class = 0x16; + gf.in.output_buffer_length = 0x1000; + gf.in.input_buffer = data_blob_null; + + req[1] = smb2_getinfo_send(tree, &gf); + + status = smb2_create_recv(req[0], tree, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_getinfo_recv(req[1], tree, &gf); + CHECK_STATUS(status, NT_STATUS_OK); + +done: + + smb2_util_close(tree, h1); + smb2_util_unlink(tree, fname1); + return ret; +} + +static bool test_compound_related1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_related1.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[2]; + struct smbXcli_tcon *saved_tcon = tree->smbXcli; + struct smbXcli_session *saved_session = tree->session->smbXcli; + + smb2_transport_credits_ask_num(tree->session->transport, 2); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 2); + + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + tree->smbXcli = smbXcli_tcon_create(tree); + smb2cli_tcon_set_values(tree->smbXcli, + NULL, /* session */ + 0xFFFFFFFF, /* tcon_id */ + 0, /* type */ + 0, /* flags */ + 0, /* capabilities */ + 0 /* maximal_access */); + + tree->session->smbXcli = smbXcli_session_shallow_copy(tree->session, + tree->session->smbXcli); + smb2cli_session_set_id_and_flags(tree->session->smbXcli, UINT64_MAX, 0); + + req[1] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + TALLOC_FREE(tree->smbXcli); + tree->smbXcli = saved_tcon; + TALLOC_FREE(tree->session->smbXcli); + tree->session->smbXcli = saved_session; + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_related2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_related2.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + struct smbXcli_tcon *saved_tcon = tree->smbXcli; + struct smbXcli_session *saved_session = tree->session->smbXcli; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + tree->smbXcli = smbXcli_tcon_create(tree); + smb2cli_tcon_set_values(tree->smbXcli, + NULL, /* session */ + 0xFFFFFFFF, /* tcon_id */ + 0, /* type */ + 0, /* flags */ + 0, /* capabilities */ + 0 /* maximal_access */); + + tree->session->smbXcli = smbXcli_session_shallow_copy(tree->session, + tree->session->smbXcli); + smb2cli_session_set_id_and_flags(tree->session->smbXcli, UINT64_MAX, 0); + + req[1] = smb2_close_send(tree, &cl); + req[2] = smb2_close_send(tree, &cl); + req[3] = smb2_close_send(tree, &cl); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + TALLOC_FREE(tree->smbXcli); + tree->smbXcli = saved_tcon; + TALLOC_FREE(tree->session->smbXcli); + tree->session->smbXcli = saved_session; + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_related3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_ioctl io; + struct smb2_create cr; + struct smb2_close cl; + const char *fname = "compound_related3.dat"; + struct smb2_request *req[3]; + NTSTATUS status; + bool ret = false; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 3); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(io); + io.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID; + io.in.file.handle = hd; + io.in.reserved2 = 0; + io.in.max_output_response = 64; + io.in.flags = 1; + + req[1] = smb2_ioctl_send(tree, &io); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_ioctl_recv(req[1], tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + ret = true; +done: + return ret; +} + +static bool test_compound_related4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related4.dat"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_ioctl io; + struct smb2_close cl; + struct smb2_request *req[4]; + NTSTATUS status; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + hd = cr.out.file.handle; + torture_comment(tctx, "set a sec desc allowing no write by CREATOR_OWNER\n"); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + torture_comment(tctx, "try open for write\n"); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + smb2_transport_compound_start(tree->session->transport, 4); + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + ZERO_STRUCT(io); + io.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID; + io.in.file.handle = hd; + io.in.flags = 1; + + req[1] = smb2_ioctl_send(tree, &io); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_ioctl_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_create_send failed\n"); + + set.set_secdesc.in.file.handle = hd; + + req[3] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_create_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_create_recv failed\n"); + + status = smb2_ioctl_recv(req[1], tree, &io); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_ioctl_recv failed\n"); + + status = smb2_close_recv(req[2], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[3]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related5(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_ioctl io; + struct smb2_close cl; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = false; + + smb2_transport_compound_start(tree->session->transport, 2); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(io); + io.in.function = FSCTL_CREATE_OR_GET_OBJECT_ID; + io.in.file.handle = hd; + io.in.flags = 1; + + req[0] = smb2_ioctl_send(tree, &io); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_ioctl_send failed\n"); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[1] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_create_send failed\n"); + + status = smb2_ioctl_recv(req[0], tree, &io); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_FILE_CLOSED, + ret, done, + "smb2_ioctl_recv failed\n"); + + status = smb2_close_recv(req[1], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_FILE_CLOSED, + ret, done, + "smb2_close_recv failed\n"); + + ret = true; + +done: + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related6(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + struct smb2_read rd; + struct smb2_write wr; + struct smb2_close cl; + NTSTATUS status; + const char *fname = "compound_related6.dat"; + struct smb2_request *req[5]; + uint8_t buf[64]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + + ZERO_STRUCT(buf); + status = smb2_util_write(tree, hd, buf, 0, ARRAY_SIZE(buf)); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_write failed\n"); + + torture_comment(tctx, "try open for read\n"); + cr.in.desired_access = SEC_FILE_READ_DATA; + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(rd); + rd.in.file.handle = hd; + rd.in.length = 1; + rd.in.offset = 0; + + req[1] = smb2_read_send(tree, &rd); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_read_send failed\n"); + + ZERO_STRUCT(wr); + wr.in.file.handle = hd; + wr.in.offset = 0; + wr.in.data = data_blob_talloc(tctx, NULL, 64); + + req[2] = smb2_write_send(tree, &wr); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_write_send failed\n"); + + ZERO_STRUCT(rd); + rd.in.file.handle = hd; + rd.in.length = 1; + rd.in.offset = 0; + + req[3] = smb2_read_send(tree, &rd); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_read_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[4] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[4], ret, done, + "smb2_close_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create_recv failed\n"); + + status = smb2_read_recv(req[1], tree, &rd); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_read_recv failed\n"); + + status = smb2_write_recv(req[2], &wr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_write_recv failed\n"); + + status = smb2_read_recv(req[3], tree, &rd); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_read_recv failed\n"); + + status = smb2_close_recv(req[4], &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close_recv failed\n"); + + done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related7(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related4.dat"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_notify nt; + struct smb2_close cl; + NTSTATUS status; + struct smb2_request *req[4]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + torture_comment(tctx, "set a sec desc allowing no write by CREATOR_OWNER\n"); + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = smb2_setinfo_file(tree, &set); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_setinfo_file failed\n"); + + torture_comment(tctx, "try open for write\n"); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + smb2_transport_compound_start(tree->session->transport, 4); + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_notify_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_close_send failed\n"); + + set.set_secdesc.in.file.handle = hd; + + req[3] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_setinfo_file_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_create_recv failed\n"); + + status = smb2_notify_recv(req[1], tree, &nt); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_notify_recv failed\n"); + + status = smb2_close_recv(req[2], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[3]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_ACCESS_DENIED, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related8(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related8.dat"; + const char *fname_nonexisting = "compound_related8.dat.void"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_notify nt; + struct smb2_close cl; + NTSTATUS status; + struct smb2_request *req[4]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + + smb2_transport_compound_start(tree->session->transport, 4); + + torture_comment(tctx, "try open for write\n"); + cr.in.fname = fname_nonexisting; + cr.in.create_disposition = NTCREATEX_DISP_OPEN; + + req[0] = smb2_create_send(tree, &cr); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_create_send failed\n"); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_notify_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[2] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_close_send failed\n"); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + req[3] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[3], ret, done, + "smb2_setinfo_file_send failed\n"); + + status = smb2_create_recv(req[0], tree, &cr); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_create_recv failed\n"); + + status = smb2_notify_recv(req[1], tree, &nt); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_notify_recv failed\n"); + + status = smb2_close_recv(req[2], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[3]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_related9(struct torture_context *tctx, + struct smb2_tree *tree) +{ + const char *fname = "compound_related9.dat"; + struct security_descriptor *sd = NULL; + struct smb2_handle hd; + struct smb2_create cr; + union smb_setfileinfo set; + struct smb2_notify nt; + struct smb2_close cl; + NTSTATUS status; + struct smb2_request *req[3]; + bool ret = true; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.level = RAW_OPEN_SMB2; + cr.in.create_flags = 0; + cr.in.desired_access = SEC_STD_READ_CONTROL | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER; + cr.in.create_options = 0; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + cr.in.alloc_size = 0; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS; + cr.in.security_flags = 0; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + hd = cr.out.file.handle; + + smb2_transport_compound_start(tree->session->transport, 3); + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + + req[0] = smb2_notify_send(tree, &nt); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_notify_send failed\n"); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + req[1] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_close_send failed\n"); + + sd = security_descriptor_dacl_create(tctx, + 0, NULL, NULL, + SID_CREATOR_OWNER, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_RIGHTS_FILE_READ | SEC_STD_ALL, + 0, + NULL); + torture_assert_not_null_goto(tctx, sd, ret, done, + "security_descriptor_dacl_create failed\n"); + + set.set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + set.set_secdesc.in.file.handle = hd; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + req[2] = smb2_setinfo_file_send(tree, &set); + torture_assert_not_null_goto(tctx, req[2], ret, done, + "smb2_setinfo_file_send failed\n"); + + status = smb2_notify_recv(req[0], tree, &nt); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_INVALID_PARAMETER, + ret, done, + "smb2_notify_recv failed\n"); + + status = smb2_close_recv(req[1], &cl); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_INVALID_PARAMETER, + ret, done, + "smb2_close_recv failed\n"); + + status = smb2_setinfo_recv(req[2]); + torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_INVALID_PARAMETER, + ret, done, + "smb2_setinfo_recv failed\n"); + +done: + smb2_util_unlink(tree, fname); + smb2_tdis(tree); + smb2_logoff(tree->session); + return ret; +} + +static bool test_compound_padding(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle h; + struct smb2_create cr; + struct smb2_read r; + const char *fname = "compound_read.dat"; + const char *sname = "compound_read.dat:foo"; + struct smb2_request *req[3]; + NTSTATUS status; + bool ret = false; + + smb2_util_unlink(tree, fname); + + /* Write file */ + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.fname = fname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree, tctx, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + h = cr.out.file.handle; + + status = smb2_util_write(tree, h, "123", 0, 3); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h); + + /* Write stream */ + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_FILE_WRITE_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.fname = sname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + status = smb2_create(tree, tctx, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + h = cr.out.file.handle; + + status = smb2_util_write(tree, h, "456", 0, 3); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h); + + /* Check compound read from basefile */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(cr); + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.desired_access = SEC_FILE_READ_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_OPEN; + cr.in.fname = fname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(r); + h.data[0] = UINT64_MAX; + h.data[1] = UINT64_MAX; + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[1] = smb2_read_send(tree, &r); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[1]) || + !smb2_request_is_ok(req[1])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 24: 16 byte read response header plus 3 + * requested bytes padded to an 8 byte boundary. + */ + CHECK_VALUE(req[1]->in.body_size, 24); + + status = smb2_read_recv(req[1], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, cr.out.file.handle); + + /* Check compound read from stream */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(cr); + cr.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; + cr.in.desired_access = SEC_FILE_READ_DATA; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.create_disposition = NTCREATEX_DISP_OPEN; + cr.in.fname = sname; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE| + NTCREATEX_SHARE_ACCESS_DELETE; + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(r); + h.data[0] = UINT64_MAX; + h.data[1] = UINT64_MAX; + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[1] = smb2_read_send(tree, &r); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[1]) || + !smb2_request_is_ok(req[1])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 24: 16 byte read response header plus 3 + * requested bytes padded to an 8 byte boundary. + */ + CHECK_VALUE(req[1]->in.body_size, 24); + + status = smb2_read_recv(req[1], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + h = cr.out.file.handle; + + /* Check 2 compound (unrelateated) reads from existing stream handle */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[0] = smb2_read_send(tree, &r); + req[1] = smb2_read_send(tree, &r); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[0]) || + !smb2_request_is_ok(req[0])) { + torture_fail(tctx, "failed to receive read request"); + } + if (!smb2_request_receive(req[1]) || + !smb2_request_is_ok(req[1])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 24: 16 byte read response header plus 3 + * requested bytes padded to an 8 byte boundary. + */ + CHECK_VALUE(req[0]->in.body_size, 24); + CHECK_VALUE(req[1]->in.body_size, 24); + + status = smb2_read_recv(req[0], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_read_recv(req[1], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + /* + * now try a single read from the stream and verify there's no padding + */ + ZERO_STRUCT(r); + r.in.file.handle = h; + r.in.length = 3; + r.in.offset = 0; + r.in.min_count = 1; + req[0] = smb2_read_send(tree, &r); + + /* + * We must do a manual smb2_request_receive() in order to be + * able to check the transport layer info, as smb2_read_recv() + * will destroy the req. smb2_read_recv() will call + * smb2_request_receive() again, but that's ok. + */ + if (!smb2_request_receive(req[0]) || + !smb2_request_is_ok(req[0])) { + torture_fail(tctx, "failed to receive read request"); + } + + /* + * size must be 19: 16 byte read response header plus 3 + * requested bytes without padding. + */ + CHECK_VALUE(req[0]->in.body_size, 19); + + status = smb2_read_recv(req[0], tree, &r); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, h); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + ret = true; +done: + return ret; +} + +static bool test_compound_create_write_close(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle handle = { .data = { UINT64_MAX, UINT64_MAX } }; + struct smb2_create create; + struct smb2_write write; + struct smb2_close close; + const char *fname = "compound_create_write_close.dat"; + struct smb2_request *req[3]; + NTSTATUS status; + bool ret = false; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(create); + create.in.security_flags = 0x00; + create.in.oplock_level = 0; + create.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + create.in.create_flags = 0x00000000; + create.in.reserved = 0x00000000; + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + create.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + create.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 3); + + req[0] = smb2_create_send(tree, &create); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(write); + write.in.file.handle = handle; + write.in.offset = 0; + write.in.data = data_blob_talloc(tctx, NULL, 1024); + + req[1] = smb2_write_send(tree, &write); + + ZERO_STRUCT(close); + close.in.file.handle = handle; + + req[2] = smb2_close_send(tree, &close); + + status = smb2_create_recv(req[0], tree, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CREATE failed."); + + status = smb2_write_recv(req[1], &write); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "WRITE failed."); + + status = smb2_close_recv(req[2], &close); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "CLOSE failed."); + + status = smb2_util_unlink(tree, fname); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "File deletion failed."); + + ret = true; +done: + return ret; +} + +static bool test_compound_unrelated1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_unrelated1.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + req[1] = smb2_close_send(tree, &cl); + req[2] = smb2_close_send(tree, &cl); + req[3] = smb2_close_send(tree, &cl); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_invalid1.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[3]; + + smb2_transport_credits_ask_num(tree->session->transport, 3); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 3); + + /* passing the first request with the related flag is invalid */ + smb2_transport_compound_set_related(tree->session->transport, true); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + req[1] = smb2_close_send(tree, &cl); + + smb2_transport_compound_set_related(tree->session->transport, false); + req[2] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + /* TODO: check why this fails with --signing=required */ + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_invalid2.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + struct smbXcli_tcon *saved_tcon = tree->smbXcli; + struct smbXcli_session *saved_session = tree->session->smbXcli; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + + tree->smbXcli = smbXcli_tcon_create(tree); + smb2cli_tcon_set_values(tree->smbXcli, + NULL, /* session */ + 0xFFFFFFFF, /* tcon_id */ + 0, /* type */ + 0, /* flags */ + 0, /* capabilities */ + 0 /* maximal_access */); + + tree->session->smbXcli = smbXcli_session_shallow_copy(tree->session, + tree->session->smbXcli); + smb2cli_session_set_id_and_flags(tree->session->smbXcli, UINT64_MAX, 0); + + req[1] = smb2_close_send(tree, &cl); + /* strange that this is not generating invalid parameter */ + smb2_transport_compound_set_related(tree->session->transport, false); + req[2] = smb2_close_send(tree, &cl); + req[3] = smb2_close_send(tree, &cl); + smb2_transport_compound_set_related(tree->session->transport, true); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); + + TALLOC_FREE(tree->smbXcli); + tree->smbXcli = saved_tcon; + TALLOC_FREE(tree->session->smbXcli); + tree->session->smbXcli = saved_session; + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid3(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status; + const char *fname = "compound_invalid3.dat"; + struct smb2_close cl; + bool ret = true; + struct smb2_request *req[5]; + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_util_unlink(tree, fname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + smb2_transport_compound_start(tree->session->transport, 5); + + req[0] = smb2_create_send(tree, &cr); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(cl); + cl.in.file.handle = hd; + req[1] = smb2_close_send(tree, &cl); + req[2] = smb2_close_send(tree, &cl); + /* flipping the related flag is invalid */ + smb2_transport_compound_set_related(tree->session->transport, true); + req[3] = smb2_close_send(tree, &cl); + req[4] = smb2_close_send(tree, &cl); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_close_recv(req[1], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[2], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[3], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + status = smb2_close_recv(req[4], &cl); + CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +static bool test_compound_invalid4(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_read rd; + NTSTATUS status; + const char *fname = "compound_invalid4.dat"; + struct smb2_close cl; + bool ret = true; + bool ok; + struct smb2_request *req[2]; + + smb2_transport_credits_ask_num(tree->session->transport, 2); + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(cr); + cr.in.security_flags = 0x00; + cr.in.oplock_level = 0; + cr.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + cr.in.create_flags = 0x00000000; + cr.in.reserved = 0x00000000; + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + cr.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + cr.in.fname = fname; + + status = smb2_create(tree, tctx, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(rd); + rd.in.file.handle = cr.out.file.handle; + rd.in.length = 1; + rd.in.offset = 0; + req[0] = smb2_read_send(tree, &rd); + + smb2_transport_compound_set_related(tree->session->transport, true); + + /* + * Send a completely bogus request as second compound + * element. This triggers smbd_smb2_request_error() in in + * smbd_smb2_request_dispatch() before calling + * smbd_smb2_request_dispatch_update_counts(). + */ + + req[1] = smb2_request_init_tree(tree, 0xff, 0x04, false, 0); + smb2_transport_send(req[1]); + + status = smb2_read_recv(req[0], tctx, &rd); + CHECK_STATUS(status, NT_STATUS_END_OF_FILE); + + ok = smb2_request_receive(req[1]); + torture_assert(tctx, ok, "Invalid request failed\n"); + CHECK_STATUS(req[1]->status, NT_STATUS_INVALID_PARAMETER); + + ZERO_STRUCT(cl); + cl.in.file.handle = cr.out.file.handle; + + status = smb2_close(tree, &cl); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_unlink(tree, fname); +done: + return ret; +} + +/* Send a compound request where we expect the last request (Create, Notify) + * to go asynchronous. This works against a Win7 server and the reply is + * sent in two different packets. */ +static bool test_compound_interim1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status = NT_STATUS_OK; + const char *dname = "compound_interim_dir"; + struct smb2_notify nt; + bool ret = true; + struct smb2_request *req[2]; + + /* Win7 compound request implementation deviates substantially from the + * SMB2 spec as noted in MS-SMB2 <159>, <162>. This, test currently + * verifies the Windows behavior, not the general spec behavior. */ + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_deltree(tree, dname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + cr.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.fname = dname; + + smb2_transport_compound_start(tree->session->transport, 2); + + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_cancel(req[1]); + status = smb2_notify_recv(req[1], tree, &nt); + CHECK_STATUS(status, NT_STATUS_CANCELLED); + + smb2_util_close(tree, cr.out.file.handle); + + smb2_deltree(tree, dname); +done: + return ret; +} + +/* Send a compound request where we expect the middle request (Create, Notify, + * GetInfo) to go asynchronous. Against Win7 the sync request succeed while + * the async fails. All are returned in the same compound response. */ +static bool test_compound_interim2(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle hd; + struct smb2_create cr; + NTSTATUS status = NT_STATUS_OK; + const char *dname = "compound_interim_dir"; + struct smb2_getinfo gf; + struct smb2_notify nt; + bool ret = true; + struct smb2_request *req[3]; + + /* Win7 compound request implementation deviates substantially from the + * SMB2 spec as noted in MS-SMB2 <159>, <162>. This, test currently + * verifies the Windows behavior, not the general spec behavior. */ + + smb2_transport_credits_ask_num(tree->session->transport, 5); + + smb2_deltree(tree, dname); + + smb2_transport_credits_ask_num(tree->session->transport, 1); + + ZERO_STRUCT(cr); + cr.in.desired_access = SEC_RIGHTS_FILE_ALL; + cr.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + cr.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + cr.in.create_disposition = NTCREATEX_DISP_CREATE; + cr.in.fname = dname; + + smb2_transport_compound_start(tree->session->transport, 3); + + req[0] = smb2_create_send(tree, &cr); + + smb2_transport_compound_set_related(tree->session->transport, true); + + hd.data[0] = UINT64_MAX; + hd.data[1] = UINT64_MAX; + + ZERO_STRUCT(nt); + nt.in.recursive = true; + nt.in.buffer_size = 0x1000; + nt.in.file.handle = hd; + nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; + nt.in.unknown = 0x00000000; + + req[1] = smb2_notify_send(tree, &nt); + + ZERO_STRUCT(gf); + gf.in.file.handle = hd; + gf.in.info_type = SMB2_0_INFO_FILE; + gf.in.info_class = 0x04; /* FILE_BASIC_INFORMATION */ + gf.in.output_buffer_length = 0x1000; + gf.in.input_buffer = data_blob_null; + + req[2] = smb2_getinfo_send(tree, &gf); + + status = smb2_create_recv(req[0], tree, &cr); + CHECK_STATUS(status, NT_STATUS_OK); + + status = smb2_notify_recv(req[1], tree, &nt); + CHECK_STATUS(status, NT_STATUS_INTERNAL_ERROR); + + status = smb2_getinfo_recv(req[2], tree, &gf); + CHECK_STATUS(status, NT_STATUS_OK); + + smb2_util_close(tree, cr.out.file.handle); + + smb2_deltree(tree, dname); +done: + return ret; +} + +/* Test compound related finds */ +static bool test_compound_find_related(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *dname = "compound_find_dir"; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, dname); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = dname; + + status = smb2_create(tree, mem_ctx, &create); + h = create.out.file.handle; + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + req[0] = smb2_find_send(tree, &f); + + smb2_transport_compound_set_related(tree->session->transport, true); + + req[1] = smb2_find_send(tree, &f); + + status = smb2_find_recv(req[0], mem_ctx, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_recv failed\n"); + + status = smb2_find_recv(req[1], mem_ctx, &f); + torture_assert_ntstatus_equal_goto(tctx, status, STATUS_NO_MORE_FILES, ret, done, "smb2_find_recv failed\n"); + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, dname); + TALLOC_FREE(mem_ctx); + return ret; +} + +/* Test compound related finds */ +static bool test_compound_find_close(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *dname = "compound_find_dir"; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h; + struct smb2_request *req = NULL; + const int num_files = 5000; + int i; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, dname); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = dname; + + smb2cli_conn_set_max_credits(tree->session->transport->conn, 256); + + status = smb2_create(tree, mem_ctx, &create); + h = create.out.file.handle; + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_FILE_ALL; + create.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + + for (i = 0; i < num_files; i++) { + create.in.fname = talloc_asprintf(mem_ctx, "%s\\file%d", + dname, i); + status = smb2_create(tree, mem_ctx, &create); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ""); + smb2_util_close(tree, create.out.file.handle); + } + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.max_response_size = 8*1024*1024; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + req = smb2_find_send(tree, &f); + + status = smb2_util_close(tree, h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_util_close failed\n"); + + status = smb2_find_recv(req, mem_ctx, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_recv failed\n"); + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, dname); + TALLOC_FREE(mem_ctx); + return ret; +} + +/* Test compound unrelated finds */ +static bool test_compound_find_unrelated(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + const char *dname = "compound_find_dir"; + struct smb2_create create; + struct smb2_find f; + struct smb2_handle h; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, dname); + + ZERO_STRUCT(create); + create.in.desired_access = SEC_RIGHTS_DIR_ALL; + create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + create.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + create.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + create.in.create_disposition = NTCREATEX_DISP_CREATE; + create.in.fname = dname; + + status = smb2_create(tree, mem_ctx, &create); + h = create.out.file.handle; + + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_create failed\n"); + + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(f); + f.in.file.handle = h; + f.in.pattern = "*"; + f.in.max_response_size = 0x100; + f.in.level = SMB2_FIND_BOTH_DIRECTORY_INFO; + + req[0] = smb2_find_send(tree, &f); + req[1] = smb2_find_send(tree, &f); + + status = smb2_find_recv(req[0], mem_ctx, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "smb2_find_recv failed\n"); + + status = smb2_find_recv(req[1], mem_ctx, &f); + torture_assert_ntstatus_equal_goto(tctx, status, STATUS_NO_MORE_FILES, ret, done, "smb2_find_recv failed\n"); + +done: + smb2_util_close(tree, h); + smb2_deltree(tree, dname); + TALLOC_FREE(mem_ctx); + return ret; +} + +static bool test_compound_async_flush_close(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle fhandle = { .data = { 0, 0 } }; + struct smb2_handle relhandle = { .data = { UINT64_MAX, UINT64_MAX } }; + struct smb2_close cl; + struct smb2_flush fl; + const char *fname = "compound_async_flush_close"; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = false; + + /* Start clean. */ + smb2_util_unlink(tree, fname); + + /* Create a file. */ + status = torture_smb2_testfile_access(tree, + fname, + &fhandle, + SEC_RIGHTS_FILE_ALL); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Now do a compound flush + close handle. */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(fl); + fl.in.file.handle = fhandle; + + req[0] = smb2_flush_send(tree, &fl); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_flush_send failed\n"); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(cl); + cl.in.file.handle = relhandle; + req[1] = smb2_close_send(tree, &cl); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_close_send failed\n"); + + status = smb2_flush_recv(req[0], &fl); + /* + * On Windows, this flush will usually + * succeed as we have nothing to flush, + * so allow NT_STATUS_OK. Once bug #15172 + * is fixed Samba will do the flush synchronously + * so allow NT_STATUS_OK. + */ + if (!NT_STATUS_IS_OK(status)) { + /* + * If we didn't get NT_STATUS_OK, we *must* + * get NT_STATUS_INTERNAL_ERROR if the flush + * goes async. + * + * For pre-bugfix #15172 Samba, the flush goes async and + * we should get NT_STATUS_INTERNAL_ERROR. + */ + torture_assert_ntstatus_equal_goto(tctx, + status, + NT_STATUS_INTERNAL_ERROR, + ret, + done, + "smb2_flush_recv didn't return " + "NT_STATUS_INTERNAL_ERROR.\n"); + } + status = smb2_close_recv(req[1], &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close_recv failed."); + + ZERO_STRUCT(fhandle); + + /* + * Do several more operations on the tree, spaced + * out by 1 sec sleeps to make sure the server didn't + * crash on the close. The sleeps are required to + * make test test for a crash reliable, as we ensure + * the pthread fsync internally finishes and accesses + * freed memory. Without them the test occassionally + * passes as we disconnect before the pthread fsync + * finishes. + */ + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ret = true; + + done: + + if (fhandle.data[0] != 0) { + smb2_util_close(tree, fhandle); + } + + smb2_util_unlink(tree, fname); + return ret; +} + +static bool test_compound_async_flush_flush(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle fhandle = { .data = { 0, 0 } }; + struct smb2_handle relhandle = { .data = { UINT64_MAX, UINT64_MAX } }; + struct smb2_flush fl1; + struct smb2_flush fl2; + const char *fname = "compound_async_flush_flush"; + struct smb2_request *req[2]; + NTSTATUS status; + bool ret = false; + + /* Start clean. */ + smb2_util_unlink(tree, fname); + + /* Create a file. */ + status = torture_smb2_testfile_access(tree, + fname, + &fhandle, + SEC_RIGHTS_FILE_ALL); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Now do a compound flush + flush handle. */ + smb2_transport_compound_start(tree->session->transport, 2); + + ZERO_STRUCT(fl1); + fl1.in.file.handle = fhandle; + + req[0] = smb2_flush_send(tree, &fl1); + torture_assert_not_null_goto(tctx, req[0], ret, done, + "smb2_flush_send (1) failed\n"); + + smb2_transport_compound_set_related(tree->session->transport, true); + + ZERO_STRUCT(fl2); + fl2.in.file.handle = relhandle; + + req[1] = smb2_flush_send(tree, &fl2); + torture_assert_not_null_goto(tctx, req[1], ret, done, + "smb2_flush_send (2) failed\n"); + + status = smb2_flush_recv(req[0], &fl1); + /* + * On Windows, this flush will usually + * succeed as we have nothing to flush, + * so allow NT_STATUS_OK. Once bug #15172 + * is fixed Samba will do the flush synchronously + * so allow NT_STATUS_OK. + */ + if (!NT_STATUS_IS_OK(status)) { + /* + * If we didn't get NT_STATUS_OK, we *must* + * get NT_STATUS_INTERNAL_ERROR if the flush + * goes async. + * + * For pre-bugfix #15172 Samba, the flush goes async and + * we should get NT_STATUS_INTERNAL_ERROR. + */ + torture_assert_ntstatus_equal_goto(tctx, + status, + NT_STATUS_INTERNAL_ERROR, + ret, + done, + "smb2_flush_recv (1) didn't return " + "NT_STATUS_INTERNAL_ERROR.\n"); + } + + /* + * If the flush is the last entry in a compound, + * it should always succeed even if it goes async. + */ + status = smb2_flush_recv(req[1], &fl2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_flush_recv (2) failed."); + + status = smb2_util_close(tree, fhandle); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed."); + ZERO_STRUCT(fhandle); + + /* + * Do several more operations on the tree, spaced + * out by 1 sec sleeps to make sure the server didn't + * crash on the close. The sleeps are required to + * make test test for a crash reliable, as we ensure + * the pthread fsync internally finishes and accesses + * freed memory. Without them the test occassionally + * passes as we disconnect before the pthread fsync + * finishes. + */ + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + sleep(1); + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + ret = true; + + done: + + if (fhandle.data[0] != 0) { + smb2_util_close(tree, fhandle); + } + + smb2_util_unlink(tree, fname); + return ret; +} + +struct torture_suite *torture_smb2_compound_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "compound"); + + torture_suite_add_1smb2_test(suite, "related1", test_compound_related1); + torture_suite_add_1smb2_test(suite, "related2", test_compound_related2); + torture_suite_add_1smb2_test(suite, "related3", + test_compound_related3); + torture_suite_add_1smb2_test(suite, "related4", + test_compound_related4); + torture_suite_add_1smb2_test(suite, "related5", + test_compound_related5); + torture_suite_add_1smb2_test(suite, "related6", + test_compound_related6); + torture_suite_add_1smb2_test(suite, "related7", + test_compound_related7); + torture_suite_add_1smb2_test(suite, "related8", + test_compound_related8); + torture_suite_add_1smb2_test(suite, "related9", + test_compound_related9); + torture_suite_add_1smb2_test(suite, "unrelated1", test_compound_unrelated1); + torture_suite_add_1smb2_test(suite, "invalid1", test_compound_invalid1); + torture_suite_add_1smb2_test(suite, "invalid2", test_compound_invalid2); + torture_suite_add_1smb2_test(suite, "invalid3", test_compound_invalid3); + torture_suite_add_1smb2_test( + suite, "invalid4", test_compound_invalid4); + torture_suite_add_1smb2_test(suite, "interim1", test_compound_interim1); + torture_suite_add_1smb2_test(suite, "interim2", test_compound_interim2); + torture_suite_add_1smb2_test(suite, "compound-break", test_compound_break); + torture_suite_add_1smb2_test(suite, "compound-padding", test_compound_padding); + torture_suite_add_1smb2_test(suite, "create-write-close", + test_compound_create_write_close); + + suite->description = talloc_strdup(suite, "SMB2-COMPOUND tests"); + + return suite; +} + +struct torture_suite *torture_smb2_compound_find_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "compound_find"); + + torture_suite_add_1smb2_test(suite, "compound_find_related", test_compound_find_related); + torture_suite_add_1smb2_test(suite, "compound_find_unrelated", test_compound_find_unrelated); + torture_suite_add_1smb2_test(suite, "compound_find_close", test_compound_find_close); + + suite->description = talloc_strdup(suite, "SMB2-COMPOUND-FIND tests"); + + return suite; +} + +struct torture_suite *torture_smb2_compound_async_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, + "compound_async"); + + torture_suite_add_1smb2_test(suite, "flush_close", + test_compound_async_flush_close); + torture_suite_add_1smb2_test(suite, "flush_flush", + test_compound_async_flush_flush); + + suite->description = talloc_strdup(suite, "SMB2-COMPOUND-ASYNC tests"); + + return suite; +} |