1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00
samba-mirror/source4/torture/smb2/lease.c
Ralph Boehme 14f3f88a3c smbtorture: Directory Leases vs unlink
Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
2024-11-05 14:39:31 +00:00

7577 lines
231 KiB
C

/*
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)); \
} else { \
CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[0], 0); \
CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[1], 0); \
} \
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 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle hnew = {};
NTSTATUS status;
const char *fname = "lease_upgrade.dat";
bool ret = true;
uint32_t caps;
caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle 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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle 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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle 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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
NTSTATUS status;
const char *fname = "lease_statopen.dat";
bool ret = true;
uint32_t caps;
caps = smb2cli_conn_server_capabilities(
tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
NTSTATUS status;
const char *fname = "lease_statopen2.dat";
bool ret = true;
uint32_t caps;
caps = smb2cli_conn_server_capabilities(
tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
NTSTATUS status;
const char *fname = "lease_statopen3.dat";
bool ret = true;
uint32_t caps;
caps = smb2cli_conn_server_capabilities(
tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
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 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle 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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "leases are not supported");
torture_assert_goto(tctx, caps & SMB2_CAP_DIRECTORY_LEASING, ret, done,
"SMB3 Directory Leases are not supported\n");
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;
}
/*
* Checks server accepts "RWH", "RH" and "R" lease request and grants at most
* (lease_request & "RH"), so no "W", but "R" without "H" if requested.
*/
static bool test_dirlease_leases(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 = {};
NTSTATUS status;
const char *dname = "test_dirlease_leases_dir";
bool ret = true;
uint32_t caps;
caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "leases are not supported");
torture_assert_goto(tctx, caps & SMB2_CAP_DIRECTORY_LEASING, ret, done,
"SMB3 Directory Leases are not supported\n");
smb2_deltree(tree, dname);
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Request "RWH" -> grant "RH" */
ZERO_STRUCT(io);
smb2_lease_v2_create_share(&io, &ls, true, dname,
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_LEASE_V2(&io, "RH", true, LEASE1,
0, 0, ++ls.lease_epoch);
smb2_util_close(tree, h1);
/* Request "RW" -> grant "R" */
ZERO_STRUCT(io);
smb2_lease_v2_create_share(&io, &ls, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RW"),
0x11);
status = smb2_create(tree, mem_ctx, &io);
CHECK_STATUS(status, NT_STATUS_OK);
h1 = io.out.file.handle;
CHECK_LEASE_V2(&io, "R", true, LEASE1,
0, 0, ++ls.lease_epoch);
smb2_util_close(tree, h1);
/* Request "RH" -> grant "RH" */
ZERO_STRUCT(io);
smb2_lease_v2_create_share(&io, &ls, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RH"),
0x11);
status = smb2_create(tree, mem_ctx, &io);
CHECK_STATUS(status, NT_STATUS_OK);
h1 = io.out.file.handle;
CHECK_LEASE_V2(&io, "RH", true, LEASE1,
0, 0, ++ls.lease_epoch);
smb2_util_close(tree, h1);
/* Request "R" -> grant "R" */
ZERO_STRUCT(io);
smb2_lease_v2_create_share(&io, &ls, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("R"),
0x11);
status = smb2_create(tree, mem_ctx, &io);
CHECK_STATUS(status, NT_STATUS_OK);
h1 = io.out.file.handle;
CHECK_LEASE_V2(&io, "R", true, LEASE1,
0, 0, ++ls.lease_epoch);
smb2_util_close(tree, h1);
done:
smb2_util_close(tree, h1);
smb2_deltree(tree, dname);
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 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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_rearm_dirlease(TALLOC_CTX *mem_ctx,
struct torture_context *tctx,
struct smb2_tree *tree,
const char *dname,
uint64_t lease_key,
uint16_t *lease_epoch)
{
struct smb2_create io;
struct smb2_lease ls;
NTSTATUS status;
bool ret = true;
smb2_lease_v2_create_share(&io,
&ls,
true,
dname,
smb2_util_share_access("RWD"),
lease_key,
NULL,
smb2_util_lease_state("RH"),
*lease_epoch);
io.in.create_disposition = NTCREATEX_DISP_OPEN;
status = smb2_create(tree, mem_ctx, &io);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "rearm failed\n");
smb2_util_close(tree, io.out.file.handle);
(*lease_epoch)++;
CHECK_LEASE_V2(&io, "RH", true, lease_key, 0, 0, *lease_epoch);
done:
return ret;
}
static bool test_lease_v2_request(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create io;
struct smb2_lease ls1, ls3, ls4, dirlease;
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
struct smb2_handle h4 = {};
struct smb2_handle h5 = {};
struct smb2_write w;
struct smb2_lease tr2_ls1;
struct smb2_request *req = NULL;
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "leases are not supported");
torture_assert_goto(tctx, caps & SMB2_CAP_DIRECTORY_LEASING, ret, done,
"SMB3 Directory Leases are not supported\n");
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, &dirlease, 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, ++dirlease.lease_epoch);
/*
* TEST: second client opens the same directory as first client,
* triggering a sharing violation
*/
ZERO_STRUCT(io);
smb2_lease_v2_create_share(&io, &tr2_ls1, true, dname,
smb2_util_share_access(""),
LEASE3, NULL,
smb2_util_lease_state("RHW"),
0x22);
status = smb2_create(tree2, mem_ctx, &io);
torture_assert_ntstatus_equal_goto(
tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done,
"CREATE didn't fail with NT_STATUS_SHARING_VIOLATION\n");
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "R", LEASE2, ++dirlease.lease_epoch);
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = test_rearm_dirlease(mem_ctx, tctx, tree, dname, LEASE2, &dirlease.lease_epoch);
torture_assert_goto(tctx, ret == true, ret, done, "Rearm dirlease failed\n");
/*
* TEST: second client opens the same directory as first client,
* triggering a sharing violation, first client closes his handle, open
* should pass.
*/
lease_break_info.lease_skip_ack = true;
ZERO_STRUCT(io);
smb2_lease_v2_create_share(&io, &tr2_ls1, true, dname,
smb2_util_share_access(""),
LEASE3, NULL,
smb2_util_lease_state("RHW"),
0x22);
req = smb2_create_send(tree2, &io);
torture_assert(tctx, req != NULL, "smb2_create_send");
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "R", LEASE2, ++dirlease.lease_epoch);
status = smb2_util_close(tree, h2);
CHECK_STATUS(status, NT_STATUS_OK);
status = smb2_create_recv(req, tctx, &io);
CHECK_STATUS(status, NT_STATUS_OK);
h2 = io.out.file.handle;
status = smb2_util_close(tree2, h2);
CHECK_STATUS(status, NT_STATUS_OK);
/* Reopen directory for subsequent tests */
ZERO_STRUCT(io);
smb2_lease_v2_create_share(&io, &dirlease, 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_LEASE_V2(&io, "RH", true, LEASE2, 0, 0, ++dirlease.lease_epoch);
torture_reset_lease_break_info(tctx, &lease_break_info);
/*
* TEST: create file in a directory with dirlease with valid parent key
* -> no break
*/
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);
/*
* TEST: create file in a directory with dirlease with invalid parent
* key -> break
*/
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, ++dirlease.lease_epoch);
/*
* TEST: Write on handle without valid parent key -> break
*/
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = test_rearm_dirlease(mem_ctx, tctx, tree, dname, LEASE2, &dirlease.lease_epoch);
torture_assert_goto(tctx, ret == true, ret, done, "Rearm dirlease failed\n");
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);
ZERO_STRUCT(h4);
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", LEASE2, ++dirlease.lease_epoch);
/*
* TEST: Write on handle with valid parent key -> no break
*/
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = test_rearm_dirlease(mem_ctx, tctx, tree, dname, LEASE2, &dirlease.lease_epoch);
torture_assert_goto(tctx, ret == true, ret, done, "Rearm dirlease failed\n");
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);
smb_msleep(4000);
CHECK_NO_BREAK(tctx);
smb2_util_close(tree, h3);
ZERO_STRUCT(h3);
CHECK_NO_BREAK(tctx);
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_flags_breaking(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct smb2_create c = {};
struct smb2_lease ls = {};
struct smb2_handle h = {};
const char *fname = "lease_v2_epoch1.dat";
enum protocol_types protocol;
uint32_t caps;
NTSTATUS status;
bool ret = true;
caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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_lease_v2_create_share(&c, &ls, false, fname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RHW"),
0x4711);
ls.lease_flags |= SMB2_LEASE_FLAG_BREAK_IN_PROGRESS;
status = smb2_create(tree, tree, &c);
CHECK_STATUS(status, NT_STATUS_OK);
h = c.out.file.handle;
CHECK_CREATED(&c, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE_V2(&c, "RHW", true, LEASE1, 0, 0, ls.lease_epoch + 1);
done:
smb2_util_close(tree, h);
smb2_util_unlink(tree, fname);
return ret;
}
/*
* Verify server ignores the parent leasekey if
* SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET is not set in the request.
*/
static bool test_lease_v2_flags_parentkey(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct smb2_create c = {};
struct smb2_lease ls = {};
struct smb2_handle h = {};
const char *fname = "lease_v2_epoch1.dat";
enum protocol_types protocol;
uint32_t caps;
NTSTATUS status;
bool ret = true;
caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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_lease_v2_create_share(&c, &ls, false, fname,
smb2_util_share_access("RWD"),
LEASE1, &LEASE1,
smb2_util_lease_state("RHW"),
0x4711);
ls.lease_flags = 0;
status = smb2_create(tree, tree, &c);
CHECK_STATUS(status, NT_STATUS_OK);
h = c.out.file.handle;
CHECK_CREATED(&c, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE_V2(&c, "RHW", true, LEASE1, 0, 0, ls.lease_epoch + 1);
done:
smb2_util_close(tree, h);
smb2_util_unlink(tree, fname);
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle hnew = {};
struct smb2_handle h1b = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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;
}
/*
* Try doing a rename overwrite where the target file is open
* with a RWH lease.
*/
static bool test_lease_v2_rename_target_overwrite(struct torture_context *tctx,
struct smb2_tree *tree)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create io;
struct smb2_create io_dst;
struct smb2_lease ls1;
struct smb2_lease ls_dst;
struct smb2_handle h = {};
struct smb2_handle h_dst = {};
union smb_setfileinfo sinfo;
const char *fname = "lease_v2_rename_overwrite_src.dat";
const char *fname_dst = "lease_v2_rename_overwrite_dst.dat";
bool ret = true;
NTSTATUS status;
uint32_t caps;
enum protocol_types protocol;
struct smb2_request *rename_req = NULL;
caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
/* Create the target file with a lease and leave open. */
ZERO_STRUCT(io_dst);
smb2_lease_v2_create_share(&io_dst, &ls_dst, false, fname_dst,
smb2_util_share_access("RWD"),
LEASE2, NULL,
smb2_util_lease_state("RHW"),
0x4711);
status = smb2_create(tree, mem_ctx, &io_dst);
CHECK_STATUS(status, NT_STATUS_OK);
h_dst = io_dst.out.file.handle;
CHECK_CREATED(&io_dst, CREATED, FILE_ATTRIBUTE_ARCHIVE);
ls_dst.lease_epoch += 1;
CHECK_LEASE_V2(&io_dst, "RHW", true, LEASE2, 0, 0, ls_dst.lease_epoch);
/*
* Now rename - should break the target lease then return
* ACCESS_DENIED.
* */
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_ACCESS_DENIED);
CHECK_BREAK_INFO_V2(tree->session->transport,
"RWH", "RW", LEASE2, ls_dst.lease_epoch + 1);
torture_reset_lease_break_info(tctx, &lease_break_info);
lease_break_info.lease_skip_ack = true;
/*
* Do the rename again, this time there's no h-lease on the dst anymore,
* so we should get no break and the rename should still fail.
*/
torture_reset_lease_break_info(tctx, &lease_break_info);
status = smb2_setinfo_file(tree, &sinfo);
CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED);
CHECK_NO_BREAK(tctx);
/*
* Do the rename again, but this time close the handle on the
* destination when receiving the h-lease break.
*/
torture_reset_lease_break_info(tctx, &lease_break_info);
lease_break_info.lease_skip_ack = true;
status = smb2_util_close(tree, h_dst);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCT(h_dst);
status = smb2_create(tree, mem_ctx, &io_dst);
CHECK_STATUS(status, NT_STATUS_OK);
h_dst = io_dst.out.file.handle;
ls_dst.lease_epoch += 1;
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;
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");
CHECK_BREAK_INFO_V2(tree->session->transport,
"RWH", "RW", LEASE2, ls_dst.lease_epoch + 1);
status = smb2_util_close(tree, h_dst);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCT(h_dst);
/* Get the rename reply. */
status = smb2_setinfo_recv(rename_req);
CHECK_STATUS(status, NT_STATUS_OK);
done:
smb2_util_close(tree, h);
smb2_util_close(tree, h_dst);
smb2_util_unlink(tree, fname);
smb2_util_unlink(tree, fname_dst);
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
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;
union smb_setfileinfo sfinfo = {};
caps = smb2cli_conn_server_capabilities(
tree1->session->transport->conn);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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);
smb2_util_close(tree1, h1);
torture_comment(tctx, "Client 1 recreates file with RH lease\n");
torture_reset_lease_break_info(tctx, &lease_break_info);
smb2_lease_create(&io1, &ls1, false, fname, LEASE1,
smb2_util_lease_state("RH"));
status = smb2_create(tree1, mem_ctx, &io1);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"create failed\n");
h1 = io1.out.file.handle;
CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io1, "RH", true, LEASE1, 0);
CHECK_VAL(lease_break_info.count, 0);
torture_comment(tctx, "Client 2 opens with RH lease\n");
smb2_lease_create(&io2, &ls2, false, fname, LEASE2,
smb2_util_lease_state("RH"));
status = smb2_create(tree2, mem_ctx, &io2);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"create failed\n");
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, 0);
torture_comment(tctx, "Client 2 sets delete on close, "
"triggering lease break\n");
sfinfo.disposition_info.in.delete_on_close = 1;
sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION;
sfinfo.generic.in.file.handle = h2;
status = smb2_setinfo_file(tree2, &sfinfo);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"Set DELETE_ON_CLOSE disposition "
"returned un expected status.\n");
CHECK_LEASE(&io1, "RH", true, LEASE1, 0);
CHECK_VAL(lease_break_info.count, 1);
status = smb2_util_close(tree2, h2);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCT(h2);
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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 = {};
struct smb2_handle h2 = {};
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);
torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "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;
}
static bool test_initial_delete_tdis(struct torture_context *tctx,
struct smb2_tree *tree1)
{
struct smb2_tree *tree2 = NULL;
struct smb2_create c = {};
struct smb2_handle h1 = {};
struct smb2_lease lease1 = {};
const char *fname = "test_initial_delete_tdis.dat";
NTSTATUS status;
bool ret = true;
tree1->session->transport->lease.handler = torture_lease_handler;
tree1->session->transport->lease.private_data = tree1;
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = torture_smb2_connection(tctx, &tree2);
torture_assert_goto(tctx, ret, ret, done, "torture_smb2_connection failed\n");
smb2_util_unlink(tree1, fname);
smb2_lease_v2_create_share(&c, &lease1, false, fname,
smb2_util_share_access("RWD"),
LEASE1,
NULL,
smb2_util_lease_state("RH"),
0);
status = smb2_create(tree1, tree1, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
c = (struct smb2_create) {
.in.desired_access = SEC_RIGHTS_FILE_ALL ,
.in.file_attributes = FILE_ATTRIBUTE_NORMAL,
.in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
.in.create_disposition = NTCREATEX_DISP_OPEN,
.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE,
.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS,
.in.fname = fname,
};
status = smb2_create(tree2, tree2, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
status = smb2_tdis(tree2);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
CHECK_BREAK_INFO_V2(tree1->session->transport,
"RH", "R",
LEASE1,
2);
status = smb2_util_close(tree1, h1);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCT(h1);
c = (struct smb2_create) {
.in.desired_access = SEC_RIGHTS_FILE_ALL ,
.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(tree1, tree1, &c);
torture_assert_ntstatus_equal_goto(tctx, status,
NT_STATUS_OBJECT_NAME_NOT_FOUND,
ret, done,
"file still there?\n");
done:
smb2_util_unlink(tree1, fname);
return ret;
}
static bool test_initial_delete_logoff(struct torture_context *tctx,
struct smb2_tree *tree1)
{
struct smb2_tree *tree2 = NULL;
struct smb2_create c = {};
struct smb2_handle h1 = {};
struct smb2_lease lease1 = {};
const char *fname = "test_initial_delete_logoff.dat";
NTSTATUS status;
bool ret = true;
tree1->session->transport->lease.handler = torture_lease_handler;
tree1->session->transport->lease.private_data = tree1;
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = torture_smb2_connection(tctx, &tree2);
torture_assert_goto(tctx, ret, ret, done, "torture_smb2_connection failed\n");
smb2_util_unlink(tree2, fname);
smb2_lease_v2_create_share(&c, &lease1, false, fname,
smb2_util_share_access("RWD"),
LEASE1,
NULL,
smb2_util_lease_state("RH"),
0);
status = smb2_create(tree1, tree1, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
c = (struct smb2_create) {
.in.desired_access = SEC_RIGHTS_FILE_ALL ,
.in.file_attributes = FILE_ATTRIBUTE_NORMAL,
.in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
.in.create_disposition = NTCREATEX_DISP_OPEN,
.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE,
.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS,
.in.fname = fname,
};
status = smb2_create(tree2, tree2, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
status = smb2_logoff(tree2->session);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
CHECK_BREAK_INFO_V2(tree1->session->transport,
"RH", "R",
LEASE1,
2);
status = smb2_util_close(tree1, h1);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCT(h1);
c = (struct smb2_create) {
.in.desired_access = SEC_RIGHTS_FILE_ALL ,
.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(tree1, tree1, &c);
torture_assert_ntstatus_equal_goto(tctx, status,
NT_STATUS_OBJECT_NAME_NOT_FOUND,
ret, done,
"file still there?\n");
done:
return ret;
}
static bool test_initial_delete_disconnect(struct torture_context *tctx,
struct smb2_tree *tree1)
{
struct smb2_tree *tree2 = NULL;
struct smb2_create c = {};
struct smb2_handle h1 = {};
struct smb2_lease lease1 = {};
const char *fname = "test_initial_delete_disconnect.dat";
NTSTATUS status;
bool ret = true;
tree1->session->transport->lease.handler = torture_lease_handler;
tree1->session->transport->lease.private_data = tree1;
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = torture_smb2_connection(tctx, &tree2);
torture_assert_goto(tctx, ret, ret, done, "torture_smb2_connection failed\n");
smb2_util_unlink(tree2, fname);
smb2_lease_v2_create_share(&c, &lease1, false, fname,
smb2_util_share_access("RWD"),
LEASE1,
NULL,
smb2_util_lease_state("RH"),
0);
status = smb2_create(tree1, tree1, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
c = (struct smb2_create) {
.in.desired_access = SEC_RIGHTS_FILE_ALL ,
.in.file_attributes = FILE_ATTRIBUTE_NORMAL,
.in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
.in.create_disposition = NTCREATEX_DISP_OPEN,
.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE,
.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS,
.in.fname = fname,
};
status = smb2_create(tree2, tree2, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
TALLOC_FREE(tree2);
CHECK_BREAK_INFO_V2(tree1->session->transport,
"RH", "R",
LEASE1,
2);
status = smb2_util_close(tree1, h1);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCT(h1);
c = (struct smb2_create) {
.in.desired_access = SEC_RIGHTS_FILE_ALL ,
.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(tree1, tree1, &c);
torture_assert_ntstatus_equal_goto(tctx, status,
NT_STATUS_OBJECT_NAME_NOT_FOUND,
ret, done,
"file still there?\n");
done:
return ret;
}
struct rename_tcase_open {
bool hlease;
bool close_on_break;
};
struct rename_tcase {
const char *name;
bool disabled;
struct rename_tcase_open o1;
struct rename_tcase_open o2;
bool do_o3;
struct rename_tcase_open o3;
NTSTATUS status;
};
static bool torture_rename_dir_openfile_do(struct torture_context *tctx,
struct smb2_tree *tree1,
struct smb2_tree *tree2,
struct rename_tcase *t)
{
struct smb2_create c = {};
union smb_setfileinfo sinfo = {};
struct smb2_handle d1 = {};
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
struct smb2_handle h3 = {};
struct smb2_handle *h = NULL;
struct smb2_lease *please1 = NULL;
struct smb2_lease *please2 = NULL;
struct smb2_lease *please3 = NULL;
struct smb2_lease lease1 = {};
struct smb2_lease lease2 = {};
struct smb2_lease lease3 = {};
struct smb2_request *req = NULL;
struct smb2_lease_break_ack ack = {};
struct rename_tcase_open *to = NULL;
const char *dname = "torture_rename_dir_openfile_dir";
const char *fname1 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file1";
const char *fname2 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file2";
const char *fname3 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file3";
const char *new_dname = "torture_rename_dir_openfile_dir-renamed";
bool expect_immediate_fail = false;
bool ret = true;
NTSTATUS status;
torture_comment(tctx, "Subtest: %s\n", t->name);
if (t->disabled) {
torture_comment(tctx, "...skipped\n");
return true;
}
tree2->session->transport->lease.handler = torture_lease_handler;
tree2->session->transport->lease.private_data = tree2;
torture_reset_lease_break_info(tctx, &lease_break_info);
lease_break_info.lease_skip_ack = true;
smb2_deltree(tree1, dname);
smb2_deltree(tree1, new_dname);
torture_comment(tctx, "Creating base directory\n");
smb2_lease_v2_create_share(&c, NULL, true, dname,
smb2_util_share_access("RWD"),
0,
NULL,
smb2_util_lease_state(""),
0);
status = smb2_create(tree1, tree1, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d1 = c.out.file.handle;
torture_comment(tctx, "Creating test file1\n");
if (t->o1.hlease) {
please1 = &lease1;
}
smb2_lease_v2_create_share(&c, please1, false, fname1,
smb2_util_share_access("RWD"),
LEASE1,
NULL,
smb2_util_lease_state("RH"),
0);
status = smb2_create(tree2, tree2, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
torture_comment(tctx, "Creating test file2\n");
if (t->o2.hlease) {
please2 = &lease2;
}
smb2_lease_v2_create_share(&c, please2, false, fname2,
smb2_util_share_access("RWD"),
LEASE2,
NULL,
smb2_util_lease_state("RH"),
0);
status = smb2_create(tree2, tree2, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h2 = c.out.file.handle;
torture_comment(tctx, "Renaming directory\n");
sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
sinfo.rename_information.in.file.handle = d1;
sinfo.rename_information.in.new_name = new_dname;
req = smb2_setinfo_file_send(tree1, &sinfo);
torture_assert_goto(tctx, req != NULL, ret, done,
"smb2_setinfo_file_send");
torture_assert(tctx, req->state == SMB2_REQUEST_RECV, "bad req state");
if (t->o1.hlease || t->o2.hlease) {
/* Get the first break */
torture_wait_for_lease_break(tctx);
if (lease_break_info.count == 0) {
/*
* If one of the two opens was without a h-lease, the
* scan for opens might hit the open without h-lease
* first triggering an immediate STATUS_ACCESS_DENIED
* for the rename without sending out any lease break.
*/
torture_assert_goto(tctx, (!t->o1.hlease || !t->o2.hlease),
ret, done,
"Expected only one hlease when getting no hlease break\n");
status = smb2_setinfo_recv(req);
torture_assert_ntstatus_equal_goto(tctx, status, t->status, ret, done,
"Rename didn't work as expected\n");
goto done;
}
if (lease_break_info.lease_break.current_lease.lease_key.data[0] == LEASE1 &&
lease_break_info.lease_break.current_lease.lease_key.data[1] == ~LEASE1)
{
torture_comment(tctx, "Got break for file 1\n");
please1 = &lease1;
h = &h1;
to = &t->o1;
} else {
torture_comment(tctx, "Got break for file 2\n");
please1 = &lease2;
h = &h2;
to = &t->o2;
}
please1->lease_epoch += 2;
CHECK_BREAK_INFO_V2_NOWAIT(tree2->session->transport,
"RH", "R",
please1->lease_key.data[0],
please1->lease_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);
lease_break_info.lease_skip_ack = true;
if (to->close_on_break) {
status = smb2_util_close(tree2, *h);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCTP(h);
} else {
status = smb2_lease_break_ack(tree2, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"ack failed\n");
expect_immediate_fail = true;
}
}
if (t->do_o3) {
torture_comment(tctx, "Doing additional open after first break\n");
if (t->o3.hlease) {
please3 = &lease3;
}
smb2_lease_v2_create_share(&c, please3, false, fname3,
smb2_util_share_access("RWD"),
LEASE3,
NULL,
smb2_util_lease_state("RH"),
0);
status = smb2_create(tree2, tree2, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h3 = c.out.file.handle;
}
if (!expect_immediate_fail && t->o1.hlease && t->o2.hlease) {
/* Get the second break */
torture_wait_for_lease_break(tctx);
if (lease_break_info.lease_break.current_lease.lease_key.data[0] == LEASE1 &&
lease_break_info.lease_break.current_lease.lease_key.data[1] == ~LEASE1)
{
torture_comment(tctx, "Got break for file 1\n");
please1 = &lease1;
h = &h1;
to = &t->o1;
} else {
torture_comment(tctx, "Got break for file 2\n");
please1 = &lease2;
h = &h2;
to = &t->o2;
}
please1->lease_epoch += 2;
CHECK_BREAK_INFO_V2_NOWAIT(tree2->session->transport,
"RH", "R",
please1->lease_key.data[0],
please1->lease_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);
lease_break_info.lease_skip_ack = true;
if (to->close_on_break) {
status = smb2_util_close(tree2, *h);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_close failed\n");
ZERO_STRUCTP(h);
} else {
status = smb2_lease_break_ack(tree2, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"ack failed\n");
}
}
status = smb2_setinfo_recv(req);
torture_assert_ntstatus_equal_goto(tctx, status, t->status, ret, done,
"Rename didn't work as expected\n");
done:
if (!smb2_util_handle_empty(d1)) {
smb2_util_close(tree1, d1);
}
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree2, h1);
}
if (!smb2_util_handle_empty(h2)) {
smb2_util_close(tree2, h2);
}
if (!smb2_util_handle_empty(h3)) {
smb2_util_close(tree2, h3);
}
smb2_deltree(tree1, dname);
smb2_deltree(tree1, new_dname);
return ret;
}
static bool torture_rename_dir_openfile(struct torture_context *tctx,
struct smb2_tree *tree1,
struct smb2_tree *tree2)
{
struct rename_tcase tcases[] = {
{
.name = "two-hleases-two-closes",
.o1 = { .hlease = true, .close_on_break = true },
.o2 = { .hlease = true, .close_on_break = true },
.do_o3 = false,
.status = NT_STATUS_OK,
},
{
.name = "no-hleases",
.o1 = { .hlease = false, },
.o2 = { .hlease = false, },
.do_o3 = false,
.status = NT_STATUS_ACCESS_DENIED,
},
{
.name = "two-hleases-second-hlease-close",
.o1 = { .hlease = true, .close_on_break = false },
.o2 = { .hlease = true, .close_on_break = true },
.do_o3 = false,
.status = NT_STATUS_ACCESS_DENIED,
},
{
.name = "two-hleases-first-hlease-close",
.o1 = { .hlease = true, .close_on_break = true },
.o2 = { .hlease = true, .close_on_break = false },
.do_o3 = false,
.status = NT_STATUS_ACCESS_DENIED,
},
{
.name = "first-hlease-close",
.o1 = { .hlease = true, .close_on_break = true },
.o2 = { .hlease = false, },
.do_o3 = false,
.status = NT_STATUS_ACCESS_DENIED,
},
{
.name = "second-hlease-close",
.o1 = { .hlease = false, },
.o2 = { .hlease = true, .close_on_break = true },
.do_o3 = false,
.status = NT_STATUS_ACCESS_DENIED,
},
{
.name = "two-hleases-two-closes-addopen-w-hlease",
.o1 = { .hlease = true, .close_on_break = true },
.o2 = { .hlease = true, .close_on_break = true },
.do_o3 = true,
.o3 = { .hlease = true, .close_on_break = true },
.status = NT_STATUS_ACCESS_DENIED,
},
{
.name = "two-hleases-two-closes-addopen-wo-hlease",
.o1 = { .hlease = true, .close_on_break = true },
.o2 = { .hlease = true, .close_on_break = true },
.do_o3 = true,
.o3 = { .hlease = false, },
.status = NT_STATUS_ACCESS_DENIED,
},
};
size_t i;
bool ret;
tree1->session->transport->lease.handler = torture_lease_handler;
tree1->session->transport->lease.private_data = tree2;
torture_reset_lease_break_info(tctx, &lease_break_info);
for (i = 0; i < ARRAY_SIZE(tcases); i++) {
ret = torture_rename_dir_openfile_do(tctx, tree1, tree2, &tcases[i]);
torture_assert_goto(tctx, ret, ret, done, "test failed\n");
}
done:
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_flags_breaking", test_lease_v2_flags_breaking);
torture_suite_add_1smb2_test(suite, "v2_flags_parentkey", test_lease_v2_flags_parentkey);
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);
torture_suite_add_1smb2_test(suite, "v2_rename_target_overwrite",
test_lease_v2_rename_target_overwrite);
torture_suite_add_1smb2_test(suite, "initial_delete_tdis",
test_initial_delete_tdis);
torture_suite_add_1smb2_test(suite, "initial_delete_logoff",
test_initial_delete_logoff);
torture_suite_add_1smb2_test(suite, "initial_delete_disconnect",
test_initial_delete_disconnect);
torture_suite_add_2smb2_test(suite, "rename_dir_openfile",
torture_rename_dir_openfile);
suite->description = talloc_strdup(suite, "SMB2-LEASE tests");
return suite;
}
enum dirlease_test {
DLT_SETEOF,
DLT_SETDOS,
DLT_BTIME,
DLT_MTIME,
DLT_CTIME,
DLT_ATIME,
};
static void prepare_setinfo(enum dirlease_test t,
union smb_setfileinfo *s,
struct smb2_handle *h)
{
s->generic.in.file.handle = *h;
switch (t) {
case DLT_SETEOF:
s->end_of_file_info.in.size++;
break;
case DLT_SETDOS:
s->basic_info.in.attrib ^= FILE_ATTRIBUTE_HIDDEN;
if (s->basic_info.in.attrib == 0) {
s->basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL;
}
break;
case DLT_BTIME:
s->basic_info.in.create_time++;
break;
case DLT_MTIME:
s->basic_info.in.write_time++;
break;
case DLT_CTIME:
s->basic_info.in.change_time++;
break;
case DLT_ATIME:
s->basic_info.in.access_time++;
break;
default:
break;
}
}
static bool test_dirlease_setinfo(struct torture_context *tctx,
TALLOC_CTX *mem_ctx,
enum dirlease_test t,
struct smb2_tree *tree,
struct smb2_tree *tree2,
const char *dname,
const char *dnamefname,
struct smb2_handle *dirh,
struct smb2_lease *dirlease,
union smb_setfileinfo *s)
{
struct smb2_create c;
struct smb2_lease ls1;
struct smb2_handle h1 = {};
NTSTATUS status;
bool ret = true;
/* 1. Same client */
/* 1.1. Handle with correct parent lease key -> no break */
smb2_lease_v2_create_share(&c, &ls1, false, dnamefname,
smb2_util_share_access("RWD"),
LEASE2, &LEASE1,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
prepare_setinfo(t, s, &h1);
status = smb2_setinfo_file(tree, s);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_filefailed");
CHECK_NO_BREAK(tctx);
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
/* 1.2. Handle with bad parent lease key -> break */
smb2_lease_v2_create_share(&c, &ls1, false, dnamefname,
smb2_util_share_access("RWD"),
LEASE2, &LEASE3,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
prepare_setinfo(t, s, &h1);
status = smb2_setinfo_file(tree, s);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_filefailed");
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", LEASE1, ++(dirlease->lease_epoch));
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = test_rearm_dirlease(mem_ctx, tctx, tree, dname,
LEASE1, &dirlease->lease_epoch);
torture_assert_goto(tctx, ret == true, ret, done,
"Rearm dirlease failed\n");
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
/* 1.3. Handle with no parent lease key -> break */
smb2_lease_v2_create_share(&c, &ls1, false, dnamefname,
smb2_util_share_access("RWD"),
LEASE2, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
prepare_setinfo(t, s, &h1);
status = smb2_setinfo_file(tree, s);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_filefailed");
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", LEASE1, ++(dirlease->lease_epoch));
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = test_rearm_dirlease(mem_ctx, tctx, tree, dname,
LEASE1, &dirlease->lease_epoch);
torture_assert_goto(tctx, ret == true, ret, done,
"Rearm dirlease failed\n");
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
/* 2. Second client */
/* 2.1. Handle with correct parent lease key -> no break */
smb2_lease_v2_create_share(&c, &ls1, false, dnamefname,
smb2_util_share_access("RWD"),
LEASE2, &LEASE1,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree2, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
prepare_setinfo(t, s, &h1);
status = smb2_setinfo_file(tree2, s);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_filefailed");
CHECK_NO_BREAK(tctx);
smb2_util_close(tree2, h1);
ZERO_STRUCT(h1);
/* 2.2. Handle with bad parent lease key -> break */
smb2_lease_v2_create_share(&c, &ls1, false, dnamefname,
smb2_util_share_access("RWD"),
LEASE2, &LEASE3,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree2, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
prepare_setinfo(t, s, &h1);
status = smb2_setinfo_file(tree2, s);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_filefailed");
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", LEASE1, ++(dirlease->lease_epoch));
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = test_rearm_dirlease(mem_ctx, tctx, tree, dname,
LEASE1, &dirlease->lease_epoch);
torture_assert_goto(tctx, ret == true, ret, done,
"Rearm dirlease failed\n");
smb2_util_close(tree2, h1);
ZERO_STRUCT(h1);
/* 2.3. Handle with no parent lease key -> break */
smb2_lease_v2_create_share(&c, &ls1, false, dnamefname,
smb2_util_share_access("RWD"),
LEASE2, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree2, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
prepare_setinfo(t, s, &h1);
status = smb2_setinfo_file(tree2, s);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_filefailed");
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", LEASE1, ++(dirlease->lease_epoch));
torture_reset_lease_break_info(tctx, &lease_break_info);
ret = test_rearm_dirlease(mem_ctx, tctx, tree, dname,
LEASE1, &dirlease->lease_epoch);
torture_assert_goto(tctx, ret == true, ret, done,
"Rearm dirlease failed\n");
smb2_util_close(tree2, h1);
ZERO_STRUCT(h1);
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree2, h1);
}
return ret;
}
static bool test_dirlease_seteof(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease;
struct smb2_handle dirh = {};
const char *dname = "test_dirlease_seteof_dir";
const char *dnamefname = "test_dirlease_seteof_dir\\lease.dat";
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
smb2_deltree(tree, dname);
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the test directory */
smb2_lease_v2_create_share(&c, &dirlease, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE1, 0, 0, ++dirlease.lease_epoch);
/*
* TEST: test setting EOF
*
* Test from same and second client, and test with correct, bad and no
* parent lease key.
*/
sfinfo.generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION;
ret = test_dirlease_setinfo(tctx, mem_ctx, DLT_SETEOF, tree, tree2,
dname, dnamefname,
&dirh, &dirlease, &sfinfo);
torture_assert_goto(tctx, ret, ret, done, "seteof test failed\n");
done:
if (!smb2_util_handle_empty(dirh)) {
smb2_util_close(tree, dirh);
}
smb2_deltree(tree, dname);
talloc_free(mem_ctx);
return ret;
}
static bool test_dirlease_setdos(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease;
struct smb2_handle dirh = {};
const char *dname = "test_dirlease_setdos_dir";
const char *dnamefname = "test_dirlease_setdos_dir\\lease.dat";
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
smb2_deltree(tree, dname);
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the test directory */
smb2_lease_v2_create_share(&c, &dirlease, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE1, 0, 0, ++dirlease.lease_epoch);
/*
* TEST: Test setting DOS attributes
*
* Test from same and second client, and test with correct, bad and no
* parent lease key.
*/
sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_HIDDEN;
sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
ret = test_dirlease_setinfo(tctx, mem_ctx, DLT_SETDOS, tree, tree2,
dname, dnamefname,
&dirh, &dirlease, &sfinfo);
torture_assert_goto(tctx, ret, ret, done, "setdos test failed\n");
done:
if (!smb2_util_handle_empty(dirh)) {
smb2_util_close(tree, dirh);
}
smb2_deltree(tree, dname);
talloc_free(mem_ctx);
return ret;
}
/*
* TEST: Test setting creation date
*/
static bool test_dirlease_setbtime(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease;
struct smb2_handle dirh = {};
const char *dname = "test_dirlease_setbtime_dir";
const char *dnamefname = "test_dirlease_setbtime_dir\\lease.dat";
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
smb2_deltree(tree, dname);
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the test directory */
smb2_lease_v2_create_share(&c, &dirlease, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE1, 0, 0, ++dirlease.lease_epoch);
sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
unix_to_nt_time(&sfinfo.basic_info.in.create_time, time(NULL) + 9*30*24*60*60);
ret = test_dirlease_setinfo(tctx, mem_ctx, DLT_BTIME, tree, tree2,
dname, dnamefname,
&dirh, &dirlease, &sfinfo);
torture_assert_goto(tctx, ret, ret, done, "setbtime test failed\n");
done:
if (!smb2_util_handle_empty(dirh)) {
smb2_util_close(tree, dirh);
}
smb2_deltree(tree, dname);
talloc_free(mem_ctx);
return ret;
}
/*
* TEST: Test setting modification date
*/
static bool test_dirlease_setmtime(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease;
struct smb2_handle dirh = {};
const char *dname = "test_dirlease_setmtime_dir";
const char *dnamefname = "test_dirlease_setmtime_dir\\lease.dat";
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
smb2_deltree(tree, dname);
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the test directory */
smb2_lease_v2_create_share(&c, &dirlease, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE1, 0, 0, ++dirlease.lease_epoch);
sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
unix_to_nt_time(&sfinfo.basic_info.in.create_time, time(NULL) + 9*30*24*60*60);
ret = test_dirlease_setinfo(tctx, mem_ctx, DLT_MTIME, tree, tree2,
dname, dnamefname,
&dirh, &dirlease, &sfinfo);
torture_assert_goto(tctx, ret, ret, done, "setmtime test failed\n");
done:
if (!smb2_util_handle_empty(dirh)) {
smb2_util_close(tree, dirh);
}
smb2_deltree(tree, dname);
talloc_free(mem_ctx);
return ret;
}
/*
* TEST: Test setting inode change date
*/
static bool test_dirlease_setctime(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease;
struct smb2_handle dirh = {};
const char *dname = "test_dirlease_setctime_dir";
const char *dnamefname = "test_dirlease_setctime_dir\\lease.dat";
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
smb2_deltree(tree, dname);
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the test directory */
smb2_lease_v2_create_share(&c, &dirlease, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE1, 0, 0, ++dirlease.lease_epoch);
sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
unix_to_nt_time(&sfinfo.basic_info.in.change_time, time(NULL) + 9*30*24*60*60);
ret = test_dirlease_setinfo(tctx, mem_ctx, DLT_CTIME, tree, tree2,
dname, dnamefname,
&dirh, &dirlease, &sfinfo);
torture_assert_goto(tctx, ret, ret, done, "setctime test failed\n");
done:
if (!smb2_util_handle_empty(dirh)) {
smb2_util_close(tree, dirh);
}
smb2_deltree(tree, dname);
talloc_free(mem_ctx);
return ret;
}
/*
* TEST: Test setting last access date
*/
static bool test_dirlease_setatime(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease;
struct smb2_handle dirh = {};
const char *dname = "test_dirlease_setatime_dir";
const char *dnamefname = "test_dirlease_setatime_dir\\lease.dat";
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
smb2_deltree(tree, dname);
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the test directory */
smb2_lease_v2_create_share(&c, &dirlease, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RHW"), 0);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE1, 0, 0, ++dirlease.lease_epoch);
sfinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
unix_to_nt_time(&sfinfo.basic_info.in.access_time, time(NULL) + 9*30*24*60*60);
ret = test_dirlease_setinfo(tctx, mem_ctx, DLT_ATIME, tree, tree2,
dname, dnamefname,
&dirh, &dirlease, &sfinfo);
torture_assert_goto(tctx, ret, ret, done, "setctime test failed\n");
done:
if (!smb2_util_handle_empty(dirh)) {
smb2_util_close(tree, dirh);
}
smb2_deltree(tree, dname);
talloc_free(mem_ctx);
return ret;
}
#define DLEASE1 0x0000000000000001ull
#define DLEASE2 0x0000000000000002ull
#define DLEASE3 0x0000000000000003ull
static struct dlt_rename {
const char *testname;
bool expect_srcdir_break;
bool expect_dstdir_break;
const char *srcdir;
const char *dstdir;
uint64_t srcdir_leasekey;
uint64_t dstdir_leasekey;
uint64_t parent_leasekey;
const char *srcfname;
const char *dstfname;
} dlt_renames[] = {
{
.testname = "samedir-correct-parent-leaskey",
.expect_srcdir_break = false,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE1,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir\\dstfile",
}, {
.testname = "samedir-wrong-parent-leaskey",
.expect_srcdir_break = true,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE3,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir\\dstfile",
}, {
.testname = "samedir-no-parent-leaskey",
.expect_srcdir_break = true,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = 0,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir\\dstfile",
}, {
.testname = "otherdir-correct-srcparent-leaskey",
.expect_srcdir_break = false,
.expect_dstdir_break = true,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE1,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = "otherdir-correct-dstparent-leaskey",
.expect_srcdir_break = true,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE2,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = "otherdir-wrong-parent-leaskey",
.expect_srcdir_break = true,
.expect_dstdir_break = true,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE3,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = "otherdir-no-parent-leaskey",
.expect_srcdir_break = true,
.expect_dstdir_break = true,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = 0,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = NULL,
}
};
static bool test_rename_one(struct torture_context *tctx,
struct smb2_tree *tree,
struct dlt_rename *t)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease1;
struct smb2_lease dirlease2;
struct smb2_handle dirh1 = {};
struct smb2_handle dirh2 = {};
struct smb2_lease lease1;
struct smb2_handle h1 = {};
struct smb2_lease_break_ack ack = {};
bool samedir = strequal(t->srcdir, t->dstdir);
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
torture_comment(tctx, "\nRename subtest: %s\n"
"==================================\n",
t->testname);
smb2_deltree(tree, t->srcdir);
smb2_deltree(tree, t->dstdir);
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the src directory */
smb2_lease_v2_create_share(&c, &dirlease1, true, t->srcdir,
smb2_util_share_access("RWD"),
t->srcdir_leasekey, NULL,
smb2_util_lease_state("RHW"), 0);
c.in.desired_access &= ~DELETE_ACCESS;
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, t->srcdir_leasekey, 0, 0, ++dirlease1.lease_epoch);
if (!samedir) {
/* Get an RH directory lease on the dst directory */
smb2_lease_v2_create_share(&c, &dirlease2, true, t->dstdir,
smb2_util_share_access("RWD"),
t->dstdir_leasekey, NULL,
smb2_util_lease_state("RHW"), 0);
c.in.desired_access &= ~DELETE_ACCESS;
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh2 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, t->dstdir_leasekey, 0, 0, ++dirlease2.lease_epoch);
}
/* Create the to be renamed file */
smb2_lease_v2_create_share(&c, &lease1, false, t->srcfname,
smb2_util_share_access("RWD"),
LEASE4, &t->srcdir_leasekey,
smb2_util_lease_state("RHW"),
0x33);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
/* Open the testfile, possibly with a bad parent leasekey */
smb2_lease_v2_create_share(&c, &lease1, false, t->srcfname,
smb2_util_share_access("RWD"),
LEASE4,
t->parent_leasekey != 0 ? &t->parent_leasekey : NULL,
smb2_util_lease_state("RHW"),
0x33);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
sfinfo.generic.level = RAW_SFILEINFO_RENAME_INFORMATION;
sfinfo.generic.in.file.handle = h1;
sfinfo.rename_information.in.new_name = t->dstfname;
status = smb2_setinfo_file(tree, &sfinfo);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_file failed\n");
if (t->expect_srcdir_break) {
/* Delay the ack in order to be able to possibly process two breaks */
lease_break_info.lease_skip_ack = true;
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "",
t->srcdir_leasekey,
++dirlease1.lease_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);
if (!t->expect_dstdir_break) {
CHECK_NO_BREAK(tctx);
}
}
if (t->expect_dstdir_break) {
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "",
t->dstdir_leasekey,
++dirlease2.lease_epoch);
torture_reset_lease_break_info(tctx, &lease_break_info);
if (!t->expect_srcdir_break) {
CHECK_NO_BREAK(tctx);
}
}
if (!t->expect_srcdir_break && !t->expect_dstdir_break) {
CHECK_NO_BREAK(tctx);
}
if (t->expect_srcdir_break) {
/* ack the first lease break. */
status = smb2_lease_break_ack(tree, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_lease_break_ack failed\n");
CHECK_LEASE_BREAK_ACK(&ack, "", t->srcdir_leasekey);
}
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree, h1);
}
if (!smb2_util_handle_empty(dirh1)) {
smb2_util_close(tree, dirh1);
}
if (!smb2_util_handle_empty(dirh2)) {
smb2_util_close(tree, dirh2);
}
smb2_deltree(tree, t->srcdir);
smb2_deltree(tree, t->dstdir);
return ret;
}
static bool test_rename(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct dlt_rename *t = NULL;
bool ret = true;
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
for (t = dlt_renames; t->testname != NULL; t++) {
ret = test_rename_one(tctx, tree, t);
torture_assert_goto(tctx, ret, ret, done,
talloc_asprintf(tctx, "%s failed\n",
t->testname));
}
done:
return ret;
}
static bool test_overwrite(struct torture_context *tctx,
struct smb2_tree *tree,
struct smb2_tree *tree2)
{
struct smb2_create c = {};
struct smb2_lease dirlease1 = {};
struct smb2_handle dirh1 = {};
struct smb2_lease lease1 = {};
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
struct smb2_request *req = NULL;
struct smb2_lease_break_ack ack = {};
struct smb2_lease *expected_lease1 = NULL;
struct smb2_lease *expected_lease2 = NULL;
uint64_t expected_leasekey1;
uint64_t expected_leasekey2;
const char *dname = "test_overwrite_dir";
const char *fname = "test_overwrite_dir\\fname";
int n;
NTSTATUS status;
bool ret = true;
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;
n = smb2_deltree(tree, dname);
torture_assert_goto(tctx, n != -1, ret, done, "smb2_deltree failed\n");
/* Get an RH directory lease on the directory */
smb2_lease_v2_create_share(&c, &dirlease1, true, dname,
smb2_util_share_access("RWD"),
LEASE1, NULL,
smb2_util_lease_state("RH"), 0);
c.in.desired_access &= ~DELETE_ACCESS;
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE1, 0, 0, ++dirlease1.lease_epoch);
/* Create a file with parent leasekey set*/
smb2_lease_v2_create_share(&c, &lease1, false, fname,
smb2_util_share_access("RWD"),
LEASE2, &LEASE1,
smb2_util_lease_state("RH"), 0);
c.in.desired_access &= ~DELETE_ACCESS;
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, LEASE2,
SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET, LEASE1,
++lease1.lease_epoch);
CHECK_NO_BREAK(tctx);
/* Second client opens with overwrite disposition */
c = (struct smb2_create) {
.in.desired_access = SEC_FILE_READ_DATA,
.in.share_access = NTCREATEX_SHARE_ACCESS_MASK,
.in.create_disposition = NTCREATEX_DISP_OVERWRITE,
.in.file_attributes = FILE_ATTRIBUTE_ARCHIVE,
.in.fname = fname,
};
req = smb2_create_send(tree2, &c);
torture_assert(tctx, req != NULL, "smb2_create_send failed\n");
torture_assert(tctx, req->state == SMB2_REQUEST_RECV, "req2 pending");
torture_wait_for_lease_break(tctx);
/*
* Expect two lease breaks (dir and file) and accept the lease breaks in
* any order.
*/
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;
if (ack.in.lease.lease_key.data[0] == LEASE1) {
expected_leasekey1 = LEASE1;
expected_lease1 = &dirlease1;
expected_leasekey2 = LEASE2;
expected_lease2 = &lease1;
} else {
expected_leasekey1 = LEASE2;
expected_lease1 = &lease1;
expected_leasekey2 = LEASE1;
expected_lease2 = &dirlease1;
}
/* Break 1 */
CHECK_BREAK_INFO_V2_NOWAIT(tree->session->transport,
"RH", "", expected_leasekey1,
++(expected_lease1->lease_epoch));
torture_reset_lease_break_info(tctx, &lease_break_info);
lease_break_info.lease_skip_ack = true;
status = smb2_lease_break_ack(tree, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_lease_break_ack failed\n");
CHECK_LEASE_BREAK_ACK(&ack, "", expected_leasekey1);
/* Break 2 */
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", expected_leasekey2,
++(expected_lease2->lease_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);
lease_break_info.lease_skip_ack = true;
status = smb2_lease_break_ack(tree, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_lease_break_ack failed\n");
CHECK_LEASE_BREAK_ACK(&ack, "", expected_leasekey2);
status = smb2_create_recv(req, tctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create_recv failed\n");
h2 = c.out.file.handle;
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree, h1);
}
if (!smb2_util_handle_empty(h2)) {
smb2_util_close(tree, h2);
}
if (!smb2_util_handle_empty(dirh1)) {
smb2_util_close(tree, dirh1);
}
n = smb2_deltree(tree, dname);
torture_assert(tctx, n != -1, "smb2_deltree failed\n");
return ret;
}
static struct dlt_hardlink {
const char *testname;
bool expect_srcdir_break;
bool expect_dstdir_break;
const char *srcdir;
const char *dstdir;
uint64_t srcdir_leasekey;
uint64_t dstdir_leasekey;
uint64_t parent_leasekey;
const char *srcfname;
const char *dstfname;
} dlt_hardlinks[] = {
{
.testname = "samedir-correct-parent-leaskey",
.expect_srcdir_break = false,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE1,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir\\dstfile",
}, {
.testname = "samedir-wrong-parent-leaskey",
.expect_srcdir_break = true,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE3,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir\\dstfile",
}, {
.testname = "samedir-no-parent-leaskey",
.expect_srcdir_break = true,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = 0,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir\\dstfile",
}, {
.testname = "otherdir-correct-srcparent-leaskey",
.expect_srcdir_break = false,
.expect_dstdir_break = true,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE1,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = "otherdir-correct-dstparent-leaskey",
.expect_srcdir_break = false,
.expect_dstdir_break = false,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE2,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = "otherdir-wrong-parent-leaskey",
.expect_srcdir_break = false,
.expect_dstdir_break = true,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = DLEASE3,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = "otherdir-no-parent-leaskey",
.expect_srcdir_break = false,
.expect_dstdir_break = true,
.srcdir = "test_dirlease_rename_dir",
.dstdir = "test_dirlease_rename_dir2",
.srcdir_leasekey = DLEASE1,
.dstdir_leasekey = DLEASE2,
.parent_leasekey = 0,
.srcfname = "test_dirlease_rename_dir\\srcfile",
.dstfname = "test_dirlease_rename_dir2\\dstfile",
}, {
.testname = NULL,
}
};
static bool test_hardlink_one(struct torture_context *tctx,
struct smb2_tree *tree,
struct dlt_hardlink *t)
{
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_create c;
struct smb2_lease dirlease1;
struct smb2_lease dirlease2;
struct smb2_handle dirh1 = {};
struct smb2_handle dirh2 = {};
struct smb2_lease lease1;
struct smb2_handle h1 = {};
struct smb2_lease_break_ack ack = {};
bool samedir = strequal(t->srcdir, t->dstdir);
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
torture_comment(tctx, "\nHardlink subtest: %s\n"
"==================================\n",
t->testname);
smb2_deltree(tree, t->srcdir);
smb2_deltree(tree, t->dstdir);
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Get an RH directory lease on the src directory */
smb2_lease_v2_create_share(&c, &dirlease1, true, t->srcdir,
smb2_util_share_access("RWD"),
t->srcdir_leasekey, NULL,
smb2_util_lease_state("RHW"), 0);
c.in.desired_access &= ~DELETE_ACCESS;
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, t->srcdir_leasekey, 0, 0, ++dirlease1.lease_epoch);
if (!samedir) {
/* Get an RH directory lease on the dst directory */
smb2_lease_v2_create_share(&c, &dirlease2, true, t->dstdir,
smb2_util_share_access("RWD"),
t->dstdir_leasekey, NULL,
smb2_util_lease_state("RHW"), 0);
c.in.desired_access &= ~DELETE_ACCESS;
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
dirh2 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, t->dstdir_leasekey, 0, 0, ++dirlease2.lease_epoch);
}
/* Create the to be hardlinkd file */
smb2_lease_v2_create_share(&c, &lease1, false, t->srcfname,
smb2_util_share_access("RWD"),
LEASE4, &t->srcdir_leasekey,
smb2_util_lease_state("RHW"),
0x33);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
/* Open the testfile, possibly with a bad parent leasekey */
smb2_lease_v2_create_share(&c, &lease1, false, t->srcfname,
smb2_util_share_access("RWD"),
LEASE4,
t->parent_leasekey != 0 ? &t->parent_leasekey : NULL,
smb2_util_lease_state("RHW"),
0x33);
status = smb2_create(tree, mem_ctx, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
sfinfo.generic.level = RAW_SFILEINFO_LINK_INFORMATION;
sfinfo.generic.in.file.handle = h1;
sfinfo.link_information.in.new_name = t->dstfname;
status = smb2_setinfo_file(tree, &sfinfo);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_file failed\n");
if (t->expect_srcdir_break) {
/* Delay the ack in order to be able to possibly process two breaks */
lease_break_info.lease_skip_ack = true;
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "",
t->srcdir_leasekey,
++dirlease1.lease_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);
if (!t->expect_dstdir_break) {
CHECK_NO_BREAK(tctx);
}
}
if (t->expect_dstdir_break) {
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "",
t->dstdir_leasekey,
++dirlease2.lease_epoch);
torture_reset_lease_break_info(tctx, &lease_break_info);
if (!t->expect_srcdir_break) {
CHECK_NO_BREAK(tctx);
}
}
if (!t->expect_srcdir_break && !t->expect_dstdir_break) {
CHECK_NO_BREAK(tctx);
}
if (t->expect_srcdir_break) {
/* ack the first lease break. */
status = smb2_lease_break_ack(tree, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_lease_break_ack failed\n");
CHECK_LEASE_BREAK_ACK(&ack, "", t->srcdir_leasekey);
}
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree, h1);
}
if (!smb2_util_handle_empty(dirh1)) {
smb2_util_close(tree, dirh1);
}
if (!smb2_util_handle_empty(dirh2)) {
smb2_util_close(tree, dirh2);
}
smb2_deltree(tree, t->srcdir);
smb2_deltree(tree, t->dstdir);
return ret;
}
static bool test_hardlink(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct dlt_hardlink *t = NULL;
bool ret = true;
tree->session->transport->lease.handler = torture_lease_handler;
tree->session->transport->lease.private_data = tree;
torture_reset_lease_break_info(tctx, &lease_break_info);
for (t = dlt_hardlinks; t->testname != NULL; t++) {
ret = test_hardlink_one(tctx, tree, t);
torture_assert_goto(tctx, ret, ret, done,
talloc_asprintf(tctx, "%s failed\n",
t->testname));
}
done:
return ret;
}
/*
* If the parent key of handle on which delete-on-close was set is the same as
* the parent key of last handle closed, don't break this parent lease but all
* others.
*/
static bool test_unlink_same_set_and_close(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct smb2_create c = {};
struct smb2_handle d1 = {};
struct smb2_handle d2 = {};
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
struct smb2_lease dlease1 = {};
struct smb2_lease dlease2 = {};
const uint64_t dlk1 = DLEASE1;
const uint64_t dlk2 = DLEASE2;
struct smb2_lease flease1 = {};
struct smb2_lease flease2 = {};
const char *dname = "test_unlink";
const char *fname = "test_unlink\\test_unlink.dat";
union smb_setfileinfo sfinfo = {};
NTSTATUS status;
bool ret = true;
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_deltree(tree, dname);
smb2_util_mkdir(tree, dname);
torture_setup_simple_file(tctx, tree, fname);
torture_comment(tctx, "First open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease1, true, dname,
DLEASE1, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE1, 0, 0, ++dlease1.lease_epoch);
torture_comment(tctx, "Second open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease2, true, dname,
DLEASE2, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d2 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE2, 0, 0, ++dlease2.lease_epoch);
torture_comment(tctx, "First open test file\n");
smb2_lease_v2_create(&c, &flease1, false, fname,
LEASE1, &dlk1,
smb2_util_lease_state("R"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
torture_comment(tctx, "Second open test file\n");
smb2_lease_v2_create(&c, &flease2, false, fname,
LEASE2, &dlk2,
smb2_util_lease_state("R"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h2 = c.out.file.handle;
torture_comment(tctx, "Sets delete on close on open 2\n");
sfinfo.disposition_info.in.delete_on_close = 1;
sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION;
sfinfo.generic.in.file.handle = h2;
status = smb2_setinfo_file(tree, &sfinfo);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_file failed\n");
torture_comment(tctx, "Closing first handle that has not set delete-on-close, "
"this should not trigger a break\n");
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
CHECK_NO_BREAK(tctx);
torture_comment(tctx, "Closing second and last handle will remove the file, "
"trigger a break on first directory with different "
"parent lease key\n");
smb2_util_close(tree, h2);
ZERO_STRUCT(h2);
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", DLEASE1,
++dlease1.lease_epoch);
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree, h1);
}
if (!smb2_util_handle_empty(h2)) {
smb2_util_close(tree, h2);
}
if (!smb2_util_handle_empty(d1)) {
smb2_util_close(tree, d1);
}
if (!smb2_util_handle_empty(d2)) {
smb2_util_close(tree, d2);
}
return ret;
}
/*
* When the parent key of handle on which delete-on-close was set differs from
* the parent key of last handle closed, which actually does delete the file,
* all directory leases must be broken.
*/
static bool test_unlink_different_set_and_close(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct smb2_create c = {};
struct smb2_handle d1 = {};
struct smb2_handle d2 = {};
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
struct smb2_lease dlease1 = {};
struct smb2_lease dlease2 = {};
const uint64_t dlk1 = DLEASE1;
const uint64_t dlk2 = DLEASE2;
struct smb2_lease flease1 = {};
struct smb2_lease flease2 = {};
const char *dname = "test_unlink";
const char *fname = "test_unlink\\test_unlink.dat";
union smb_setfileinfo sfinfo = {};
struct smb2_lease_break_ack ack = {};
struct smb2_lease *expected_lease1 = NULL;
struct smb2_lease *expected_lease2 = NULL;
uint64_t expected_leasekey1;
uint64_t expected_leasekey2;
NTSTATUS status;
bool ret = true;
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;
smb2_deltree(tree, dname);
smb2_util_mkdir(tree, dname);
torture_setup_simple_file(tctx, tree, fname);
torture_comment(tctx, "First open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease1, true, dname,
DLEASE1, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE1, 0, 0, ++dlease1.lease_epoch);
torture_comment(tctx, "Second open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease2, true, dname,
DLEASE2, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d2 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE2, 0, 0, ++dlease2.lease_epoch);
torture_comment(tctx, "First open test file\n");
smb2_lease_v2_create(&c, &flease1, false, fname,
LEASE1, &dlk1,
smb2_util_lease_state("R"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
torture_comment(tctx, "Second open test file\n");
smb2_lease_v2_create(&c, &flease2, false, fname,
LEASE2, &dlk2,
smb2_util_lease_state("R"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h2 = c.out.file.handle;
torture_comment(tctx, "Client 1 sets delete on close\n");
sfinfo.disposition_info.in.delete_on_close = 1;
sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION;
sfinfo.generic.in.file.handle = h1;
status = smb2_setinfo_file(tree, &sfinfo);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_setinfo_file failed\n");
torture_comment(tctx, "Closing first handle that has set delete-on-close, "
"will not delete the file and not trigger a break\n");
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
CHECK_NO_BREAK(tctx);
torture_comment(tctx, "Closing second and last handle will remove the file, "
"and trigger a break as the parent lease keys don't match\n");
smb2_util_close(tree, h2);
ZERO_STRUCT(h2);
torture_wait_for_lease_break(tctx);
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;
if (ack.in.lease.lease_key.data[0] == DLEASE1) {
expected_leasekey1 = DLEASE1;
expected_lease1 = &dlease1;
expected_leasekey2 = DLEASE2;
expected_lease2 = &dlease2;
} else {
expected_leasekey1 = DLEASE2;
expected_lease1 = &dlease2;
expected_leasekey2 = DLEASE1;
expected_lease2 = &dlease1;
}
CHECK_BREAK_INFO_V2_NOWAIT(tree->session->transport,
"RH", "", expected_leasekey1,
++(expected_lease1->lease_epoch));
torture_reset_lease_break_info(tctx, &lease_break_info);
lease_break_info.lease_skip_ack = true;
status = smb2_lease_break_ack(tree, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_lease_break_ack failed\n");
CHECK_LEASE_BREAK_ACK(&ack, "", expected_leasekey1);
CHECK_BREAK_INFO_V2_NOWAIT(tree->session->transport,
"RH", "", expected_leasekey2,
++(expected_lease2->lease_epoch));
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree, h1);
}
if (!smb2_util_handle_empty(h2)) {
smb2_util_close(tree, h2);
}
if (!smb2_util_handle_empty(d1)) {
smb2_util_close(tree, d1);
}
if (!smb2_util_handle_empty(d2)) {
smb2_util_close(tree, d2);
}
return ret;
}
/*
* If the parent key of handle on which initial delete-on-close was requested is
* the same as the parent key of last handle closed, don't break that parent
* lease but all others.
*/
static bool test_unlink_same_initial_and_close(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct smb2_create c = {};
struct smb2_handle d1 = {};
struct smb2_handle d2 = {};
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
struct smb2_lease dlease1 = {};
struct smb2_lease dlease2 = {};
const uint64_t dlk1 = DLEASE1;
const uint64_t dlk2 = DLEASE2;
struct smb2_lease flease1 = {};
struct smb2_lease flease2 = {};
const char *dname = "test_unlink";
const char *fname = "test_unlink\\test_unlink.dat";
NTSTATUS status;
bool ret = true;
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;
smb2_deltree(tree, dname);
smb2_util_mkdir(tree, dname);
torture_setup_simple_file(tctx, tree, fname);
torture_comment(tctx, "First open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease1, true, dname,
DLEASE1, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE1, 0, 0, ++dlease1.lease_epoch);
torture_comment(tctx, "Second open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease2, true, dname,
DLEASE2, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d2 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE2, 0, 0, ++dlease2.lease_epoch);
torture_comment(tctx, "First open test file with initial delete-on-close\n");
smb2_lease_v2_create(&c, &flease1, false, fname,
LEASE1, &dlk1,
smb2_util_lease_state("R"), 0);
c.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
torture_comment(tctx, "Second open test file\n");
smb2_lease_v2_create(&c, &flease2, false, fname,
LEASE2, &dlk2,
smb2_util_lease_state("R"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h2 = c.out.file.handle;
torture_comment(tctx, "Closing second handle should not trigger a lease break\n");
smb2_util_close(tree, h2);
ZERO_STRUCT(h2);
torture_comment(tctx, "Closing first handle that had initial delete-on-close, "
"must trigger single break on directory handle 2\n");
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
CHECK_BREAK_INFO_V2(tree->session->transport,
"RH", "", DLEASE2,
++dlease2.lease_epoch);
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree, h1);
}
if (!smb2_util_handle_empty(h2)) {
smb2_util_close(tree, h2);
}
if (!smb2_util_handle_empty(d1)) {
smb2_util_close(tree, d1);
}
if (!smb2_util_handle_empty(d2)) {
smb2_util_close(tree, d2);
}
return ret;
}
/*
* When the parent key of handle on which initial delete-on-close was set
* differs from the parent key of last handle closed, which actually does delete
* the file, all directory leases must be broken.
*/
static bool test_unlink_different_initial_and_close(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct smb2_create c = {};
struct smb2_handle d1 = {};
struct smb2_handle d2 = {};
struct smb2_handle h1 = {};
struct smb2_handle h2 = {};
struct smb2_lease dlease1 = {};
struct smb2_lease dlease2 = {};
const uint64_t dlk1 = DLEASE1;
const uint64_t dlk2 = DLEASE2;
struct smb2_lease flease1 = {};
struct smb2_lease flease2 = {};
const char *dname = "test_unlink";
const char *fname = "test_unlink\\test_unlink.dat";
struct smb2_lease_break_ack ack = {};
struct smb2_lease *expected_lease1 = NULL;
struct smb2_lease *expected_lease2 = NULL;
uint64_t expected_leasekey1;
uint64_t expected_leasekey2;
NTSTATUS status;
bool ret = true;
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;
smb2_deltree(tree, dname);
smb2_util_mkdir(tree, dname);
torture_setup_simple_file(tctx, tree, fname);
torture_comment(tctx, "First open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease1, true, dname,
DLEASE1, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d1 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE1, 0, 0, ++dlease1.lease_epoch);
torture_comment(tctx, "Second open test directory with RH-dirlease\n");
smb2_lease_v2_create(&c, &dlease2, true, dname,
DLEASE2, NULL,
smb2_util_lease_state("RH"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
d2 = c.out.file.handle;
CHECK_LEASE_V2(&c, "RH", true, DLEASE2, 0, 0, ++dlease2.lease_epoch);
torture_comment(tctx, "First open test file\n");
smb2_lease_v2_create(&c, &flease1, false, fname,
LEASE1, &dlk1,
smb2_util_lease_state("R"), 0);
c.in.create_options = NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h1 = c.out.file.handle;
torture_comment(tctx, "Second open test file\n");
smb2_lease_v2_create(&c, &flease2, false, fname,
LEASE2, &dlk2,
smb2_util_lease_state("R"), 0);
status = smb2_create(tree, tree, &c);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_create failed\n");
h2 = c.out.file.handle;
torture_comment(tctx, "Closing first handle that requested initial delete-on-close, "
"will not delete the file and not trigger a break\n");
smb2_util_close(tree, h1);
ZERO_STRUCT(h1);
CHECK_NO_BREAK(tctx);
torture_comment(tctx, "Closing second and last handle will remove the file, "
"and trigger a break as the parent lease keys don't match\n");
smb2_util_close(tree, h2);
ZERO_STRUCT(h2);
torture_wait_for_lease_break(tctx);
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;
if (ack.in.lease.lease_key.data[0] == DLEASE1) {
expected_leasekey1 = DLEASE1;
expected_lease1 = &dlease1;
expected_leasekey2 = DLEASE2;
expected_lease2 = &dlease2;
} else {
expected_leasekey1 = DLEASE2;
expected_lease1 = &dlease2;
expected_leasekey2 = DLEASE1;
expected_lease2 = &dlease1;
}
CHECK_BREAK_INFO_V2_NOWAIT(tree->session->transport,
"RH", "", expected_leasekey1,
++(expected_lease1->lease_epoch));
torture_reset_lease_break_info(tctx, &lease_break_info);
lease_break_info.lease_skip_ack = true;
status = smb2_lease_break_ack(tree, &ack);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_lease_break_ack failed\n");
CHECK_LEASE_BREAK_ACK(&ack, "", expected_leasekey1);
CHECK_BREAK_INFO_V2_NOWAIT(tree->session->transport,
"RH", "", expected_leasekey2,
++(expected_lease2->lease_epoch));
done:
if (!smb2_util_handle_empty(h1)) {
smb2_util_close(tree, h1);
}
if (!smb2_util_handle_empty(h2)) {
smb2_util_close(tree, h2);
}
if (!smb2_util_handle_empty(d1)) {
smb2_util_close(tree, d1);
}
if (!smb2_util_handle_empty(d2)) {
smb2_util_close(tree, d2);
}
return ret;
}
struct torture_suite *torture_smb2_dirlease_init(TALLOC_CTX *ctx)
{
struct torture_suite *suite =
torture_suite_create(ctx, "dirlease");
suite->description = talloc_strdup(suite, "SMB3 Directory Lease tests");
torture_suite_add_1smb2_test(suite, "v2_request_parent", test_lease_v2_request_parent);
torture_suite_add_2smb2_test(suite, "v2_request", test_lease_v2_request);
torture_suite_add_1smb2_test(suite, "leases", test_dirlease_leases);
torture_suite_add_2smb2_test(suite, "seteof", test_dirlease_seteof);
torture_suite_add_2smb2_test(suite, "setdos", test_dirlease_setdos);
torture_suite_add_2smb2_test(suite, "setbtime", test_dirlease_setbtime);
torture_suite_add_2smb2_test(suite, "setmtime", test_dirlease_setmtime);
torture_suite_add_2smb2_test(suite, "setctime", test_dirlease_setctime);
torture_suite_add_2smb2_test(suite, "setatime", test_dirlease_setatime);
torture_suite_add_1smb2_test(suite, "rename", test_rename);
torture_suite_add_2smb2_test(suite, "overwrite", test_overwrite);
torture_suite_add_1smb2_test(suite, "hardlink", test_hardlink);
torture_suite_add_1smb2_test(suite, "unlink_same_set_and_close", test_unlink_same_set_and_close);
torture_suite_add_1smb2_test(suite, "unlink_different_set_and_close", test_unlink_different_set_and_close);
torture_suite_add_1smb2_test(suite, "unlink_same_initial_and_close", test_unlink_same_initial_and_close);
torture_suite_add_1smb2_test(suite, "unlink_different_initial_and_close", test_unlink_different_initial_and_close);
return suite;
}