[PATCHES] smb3 create replay (for multi-channel)

Michael Adam obnox at samba.org
Wed Mar 2 21:45:11 UTC 2016


On 2016-03-02 at 13:28 -0800, Jeremy Allison wrote:
> On Wed, Mar 02, 2016 at 09:54:04PM +0100, Michael Adam wrote:
> > On 2016-03-02 at 12:05 -0800, Jeremy Allison wrote:
> > > On Tue, Mar 01, 2016 at 02:15:33AM +0100, Michael Adam wrote:
> > > > 
> > > > here is an important step toward multi channel:
> > > > Implementation of create replay.
> > > > 
> > > > 
> > > > Review appreciated.
> > > 
> > > LGTM - with the first (already applied) patches
> > > removed ! Pushed.
> > 
> > Thanks a lot Jeremy!
> > 
> > I was about to push an updated patchset with the
> > already pushed patches removed, a few cosmetic
> > cleanups and more importantly, some additional
> > new torture tests and (fairly minor) adaptions
> > of the server code for the corner cases.
> > 
> > Since your build has not been started, would
> > you like me to send a follow up patch or
> > would you consider pushing a revised patchset
> > over your currently waiting autobuild?
> 
> Sure, send me the revised and the follow-up...

Attached find the updated patchset.
Differences with the original one:

- some cosmetic clean-up of commit messages
  and test names
- added three new tests:
  - replay-dht2-oplock-lease
    (create with oplock, replay with lease)
  - replay-dht2-lease-oplock
    (create with lease, replay with oplock)
  - replay-dht2-lease3
    (replay with a different lease key)
- fixed treatment of leases in create replay
  in corner cases

Cheers - Michael

-------------- next part --------------
From bff1da9bc2e32252ae85437fef09bb8b0306e961 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Tue, 1 Mar 2016 01:14:48 +0100
Subject: [PATCH 01/15] torture:smb2: rename replay1 -> replay-commands

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            | 2 +-
 source4/torture/smb2/replay.c | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 04a0621..b1222f0 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -202,7 +202,7 @@
 ^samba3.smb2.setinfo.setinfo
 ^samba3.smb2.session.*reauth5 # some special anonymous checks?
 ^samba3.smb2.compound.interim2 # wrong return code (STATUS_CANCELLED)
-^samba3.smb2.replay.replay1
+^samba3.smb2.replay.replay-commands
 ^samba3.smb2.replay.replay2
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index 21f6f80..cef682a 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -161,7 +161,7 @@ static bool torture_oplock_ack_handler(struct smb2_transport *transport,
  * Test what happens when SMB2_FLAGS_REPLAY_OPERATION is enabled for various
  * commands. We want to verify if the server returns an error code or not.
  */
-static bool test_replay1(struct torture_context *tctx, struct smb2_tree *tree)
+static bool test_replay_commands(struct torture_context *tctx, struct smb2_tree *tree)
 {
 	bool ret = true;
 	NTSTATUS status;
@@ -175,7 +175,7 @@ static bool test_replay1(struct torture_context *tctx, struct smb2_tree *tree)
 	struct smb2_lock_element el[2];
 	struct smb2_flush f;
 	TALLOC_CTX *tmp_ctx = talloc_new(tree);
-	const char *fname = BASEDIR "\\replay1.dat";
+	const char *fname = BASEDIR "\\replay_commands.dat";
 	struct smb2_transport *transport = tree->session->transport;
 
 	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
@@ -997,7 +997,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	struct torture_suite *suite =
 		torture_suite_create(talloc_autofree_context(), "replay");
 
-	torture_suite_add_1smb2_test(suite, "replay1", test_replay1);
+	torture_suite_add_1smb2_test(suite, "replay-commands", test_replay_commands);
 	torture_suite_add_1smb2_test(suite, "replay2", test_replay2);
 	torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
 	torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
-- 
2.5.0


From cade18c935df37b43bdfcc78eb075df4cba2f9f8 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Tue, 1 Mar 2016 01:18:03 +0100
Subject: [PATCH 02/15] torture:smb2: split rename2 into multiple tests and
 extend these

- replay-regular
- replay-dhv2-oplock1
- replay-dhv2-oplock2
- replay-dhv2-oplock3

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            |   5 +-
 source4/torture/smb2/replay.c | 384 +++++++++++++++++++++++++++++++++++++-----
 2 files changed, 342 insertions(+), 47 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index b1222f0..847415e 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -203,7 +203,10 @@
 ^samba3.smb2.session.*reauth5 # some special anonymous checks?
 ^samba3.smb2.compound.interim2 # wrong return code (STATUS_CANCELLED)
 ^samba3.smb2.replay.replay-commands
-^samba3.smb2.replay.replay2
+^samba3.smb2.replay.replay-regular
+^samba3.smb2.replay.replay-dhv2-oplock1
+^samba3.smb2.replay.replay-dhv2-oplock2
+^samba3.smb2.replay.replay-dhv2-oplock3
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
 ^samba3.smb2.lock.*replay
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index cef682a..561963a 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -287,20 +287,136 @@ done:
 }
 
 /**
- * Test Durablity V2 Create Replay Detection on Single Channel. Also verify that
- * regular creates can not be replayed.
+ * Test replay detection without create GUID on single channel.
+ * Regular creates can not be replayed.
+ * The return code is unaffected of the REPLAY_OPERATION flag.
  */
-static bool test_replay2(struct torture_context *tctx, struct smb2_tree *tree)
+static bool test_replay_regular(struct torture_context *tctx,
+				struct smb2_tree *tree)
 {
 	NTSTATUS status;
 	TALLOC_CTX *mem_ctx = talloc_new(tctx);
 	struct smb2_handle _h;
 	struct smb2_handle *h = NULL;
-	struct smb2_create io, ref1, ref2;
-	struct GUID create_guid = GUID_random();
+	struct smb2_create io;
 	uint32_t perms = 0;
 	bool ret = true;
-	const char *fname = BASEDIR "\\replay2.dat";
+	const char *fname = BASEDIR "\\replay_regular.dat";
+	struct smb2_transport *transport = tree->session->transport;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h);
+	CHECK_VAL(break_info.count, 0);
+
+	torture_comment(tctx, "No replay detection for regular create\n");
+
+	perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE |
+		SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE |
+		SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA |
+		SEC_FILE_WRITE_DATA;
+
+	io = (struct smb2_create) {
+		.in.desired_access  = perms,
+		.in.file_attributes = 0,
+		.in.create_disposition = NTCREATEX_DISP_CREATE,
+		.in.share_access    = NTCREATEX_SHARE_ACCESS_DELETE,
+		.in.create_options  = 0x0,
+		.in.fname   = fname
+	};
+
+	status = smb2_create(tree, tctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_VAL(break_info.count, 0);
+	_h = io.out.file.handle;
+	h = &_h;
+	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, tctx, &io);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION);
+	CHECK_VAL(break_info.count, 0);
+
+	smb2_util_close(tree, *h);
+	h = NULL;
+	smb2_util_unlink(tree, fname);
+
+	/*
+	 * Same experiment with different create disposition.
+	 */
+	io.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	status = smb2_create(tree, tctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_VAL(break_info.count, 0);
+	_h = io.out.file.handle;
+	h = &_h;
+	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, tctx, &io);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
+	CHECK_VAL(break_info.count, 0);
+
+	smb2_util_close(tree, *h);
+	h = NULL;
+	smb2_util_unlink(tree, fname);
+
+	/*
+	 * Now with more generous share mode.
+	 */
+	io.in.share_access = smb2_util_share_access("RWD");
+	status = smb2_create(tree, tctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_VAL(break_info.count, 0);
+	_h = io.out.file.handle;
+	h = &_h;
+	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, tctx, &io);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_VAL(break_info.count, 0);
+
+done:
+	if (h != NULL) {
+		smb2_util_close(tree, *h);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
+ * Test Durablity V2 Create Replay Detection on Single Channel.
+ */
+static bool test_replay_dhv2_oplock1(struct torture_context *tctx,
+				     struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h;
+	struct smb2_handle *h = NULL;
+	struct smb2_create io, ref1;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay_dhv2_oplock1.dat";
 	struct smb2_transport *transport = tree->session->transport;
 	uint32_t share_capabilities;
 	bool share_is_so;
@@ -362,14 +478,98 @@ static bool test_replay2(struct torture_context *tctx, struct smb2_tree *tree)
 	CHECK_CREATE_OUT(&io, &ref1);
 	CHECK_VAL(break_info.count, 0);
 
+done:
+	if (h != NULL) {
+		smb2_util_close(tree, *h);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
+ * Test Durablity V2 Create Replay Detection on Single Channel.
+ * Hand in a different oplock level in the replay.
+ * Server responds with the handed in oplock level and
+ * corresponding durable status, but does not change the
+ * oplock level or durable status of the opened file.
+ */
+static bool test_replay_dhv2_oplock2(struct torture_context *tctx,
+				      struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h;
+	struct smb2_handle *h = NULL;
+	struct smb2_create io, ref1, ref2;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay_dhv2_oplock2.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 on Single "
+			      "Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h);
+	CHECK_VAL(break_info.count, 0);
+
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	ref1 = io;
+	_h = io.out.file.handle;
+	h = &_h;
+	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io.out.durable_open, false);
+	if (share_is_so) {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s"));
+		CHECK_VAL(io.out.durable_open_v2, false);
+		CHECK_VAL(io.out.timeout, 0);
+	} else {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+		CHECK_VAL(io.out.durable_open_v2, true);
+		CHECK_VAL(io.out.timeout, io.in.timeout);
+	}
+
 	/*
-	 * See how server behaves if we change some of the Create params while
-	 * Replaying. Change Share Access and Oplock Level. It seems the server
-	 * does not care for change in these parameters. The server seems to
-	 * only care for the File Name and GUID
+	 * Replay durable v2 create on single channel:
+	 *
+	 * Replay the create with a different oplock (none).
+	 * The server replies with the requested oplock level
+	 * and also only replies with durable handle based
+	 * on whether it could have been granted based on
+	 * the requested oplock type.
 	 */
 	smb2_oplock_create_share(&io, fname,
-			smb2_util_share_access("RWD"),
+			smb2_util_share_access(""),
 			smb2_util_oplock_level(""));
 	io.in.durable_open = false;
 	io.in.durable_open_v2 = true;
@@ -378,9 +578,7 @@ static bool test_replay2(struct torture_context *tctx, struct smb2_tree *tree)
 	io.in.timeout = UINT32_MAX;
 
 	/*
-	 * The output will just react on the
-	 * input, but it doesn't change the oplock
-	 * or share access values on the existing open
+	 * Adapt the response to the exepected values
 	 */
 	ref2 = ref1;
 	ref2.out.oplock_level = smb2_util_oplock_level("");
@@ -396,11 +594,17 @@ static bool test_replay2(struct torture_context *tctx, struct smb2_tree *tree)
 	CHECK_VAL(break_info.count, 0);
 
 	/*
-	 * This is a normal open, which triggers an oplock
-	 * break and still gets NT_STATUS_SHARING_VIOLATION
+	 * Prove that the open file still has a batch oplock
+	 * by breaking it with another open.
 	 */
-	io = ref1;
-	io.in.durable_open_v2 = false;
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = GUID_random();
+	io.in.timeout = UINT32_MAX;
 	status = smb2_create(tree, mem_ctx, &io);
 	CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
 
@@ -411,48 +615,133 @@ static bool test_replay2(struct torture_context *tctx, struct smb2_tree *tree)
 		ZERO_STRUCT(break_info);
 	}
 
-	smb2_util_close(tree, *h);
-	h = NULL;
-	status = smb2_util_unlink(tree, fname);
-	CHECK_STATUS(status, NT_STATUS_OK);
-	CHECK_VAL(break_info.count, 0);
+done:
+	if (h != NULL) {
+		smb2_util_close(tree, *h);
+	}
+	smb2_deltree(tree, BASEDIR);
 
-	/*
-	 * No Replay detection for regular Creates
-	 */
-	perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE |
-		SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE |
-		SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA |
-		SEC_FILE_WRITE_DATA;
+	talloc_free(tree);
+	talloc_free(mem_ctx);
 
-	io = (struct smb2_create) {
-		.in.desired_access  = perms,
-		.in.file_attributes = 0,
-		.in.create_disposition = NTCREATEX_DISP_CREATE,
-		.in.share_access    = NTCREATEX_SHARE_ACCESS_DELETE,
-		.in.create_options  = 0x0,
-		.in.fname   = fname
-	};
+	return ret;
+}
 
-	status = smb2_create(tree, tctx, &io);
+/**
+ * Test Durablity V2 Create Replay Detection on Single Channel.
+ * Replay with a different share mode. The share mode of
+ * the opened file is not changed by this.
+ */
+static bool test_replay_dhv2_oplock3(struct torture_context *tctx,
+				     struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h;
+	struct smb2_handle *h = NULL;
+	struct smb2_create io, ref1;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay_dhv2_oplock3.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 on Single "
+			      "Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h);
 	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h);
 	CHECK_VAL(break_info.count, 0);
+
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	ref1 = io;
 	_h = io.out.file.handle;
 	h = &_h;
 	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io.out.durable_open, false);
+	if (share_is_so) {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s"));
+		CHECK_VAL(io.out.durable_open_v2, false);
+		CHECK_VAL(io.out.timeout, 0);
+	} else {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+		CHECK_VAL(io.out.durable_open_v2, true);
+		CHECK_VAL(io.out.timeout, io.in.timeout);
+	}
 
-	torture_comment(tctx, "No Replay Detection for regular Create\n");
 	/*
-	 * Now replay the same create
+	 * Replay durable v2 create on single channel:
+	 *
+	 * Replay the create with a different share mode.
+	 * The server replies with the requested share
+	 * mode instead of that which is associated to
+	 * the handle.
 	 */
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access("RWD"),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
 	smb2cli_session_start_replay(tree->session->smbXcli);
-	status = smb2_create(tree, tctx, &io);
-	CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_COLLISION);
+	status = smb2_create(tree, mem_ctx, &io);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATE_OUT(&io, &ref1);
 	CHECK_VAL(break_info.count, 0);
 
-done:
-	smb2cli_session_stop_replay(tree->session->smbXcli);
+	/*
+	 * In order to prove that the different share mode in the
+	 * replayed create had no effect on the open file handle,
+	 * show that a new create yields NT_STATUS_SHARING_VIOLATION.
+	 */
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = GUID_random();
+	io.in.timeout = UINT32_MAX;
+	status = smb2_create(tree, mem_ctx, &io);
+	CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
 
+	if (!share_is_so) {
+		CHECK_VAL(break_info.count, 1);
+		CHECK_HANDLE(&break_info.handle, &ref1.out.file.handle);
+		CHECK_VAL(break_info.level, smb2_util_oplock_level("s"));
+		ZERO_STRUCT(break_info);
+	}
+
+done:
 	if (h != NULL) {
 		smb2_util_close(tree, *h);
 	}
@@ -998,7 +1287,10 @@ struct torture_suite *torture_smb2_replay_init(void)
 		torture_suite_create(talloc_autofree_context(), "replay");
 
 	torture_suite_add_1smb2_test(suite, "replay-commands", test_replay_commands);
-	torture_suite_add_1smb2_test(suite, "replay2", test_replay2);
+	torture_suite_add_1smb2_test(suite, "replay-regular", test_replay_regular);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock1", test_replay_dhv2_oplock1);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock2", test_replay_dhv2_oplock2);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock3", test_replay_dhv2_oplock3);
 	torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
 	torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
 	torture_suite_add_1smb2_test(suite, "replay5", test_replay5);
-- 
2.5.0


From 47f784c56e346ef0c5620d9fceb8a38373dc38e9 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Mon, 29 Feb 2016 19:00:42 +0100
Subject: [PATCH 03/15] torture:smb2:replay: extend CHECK_CREATE_OUT() to know
 leases

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source4/torture/smb2/replay.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index 561963a..741fa4e 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -85,6 +85,11 @@
 		__IO_OUT_VAL(__io1, __io2, persistent_open);	\
 		__IO_OUT_VAL(__io1, __io2, timeout);		\
 		__IO_OUT_VAL(__io1, __io2, blobs.num_blobs);	\
+		if ((__io1)->out.oplock_level == SMB2_OPLOCK_LEVEL_LEASE) { \
+			__IO_OUT_VAL(__io1, __io2, lease_response.lease_state);\
+			__IO_OUT_VAL(__io1, __io2, lease_response.lease_key.data[0]);\
+			__IO_OUT_VAL(__io1, __io2, lease_response.lease_key.data[1]);\
+		} \
 	} while(0)
 
 #define BASEDIR "replaytestdir"
-- 
2.5.0


From 2de4d9df819371bb459dcdbc56f267c0a855131a Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Mon, 29 Feb 2016 18:23:04 +0100
Subject: [PATCH 04/15] torture:smb2: add smb2.replay.replay-dhv2-lease1

This is a variant of the replay-dhv2-oplock1 test for leases
instead of for oplocks.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            |   1 +
 source4/torture/smb2/replay.c | 139 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 140 insertions(+)

diff --git a/selftest/knownfail b/selftest/knownfail
index 847415e..3a61c1d 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -207,6 +207,7 @@
 ^samba3.smb2.replay.replay-dhv2-oplock1
 ^samba3.smb2.replay.replay-dhv2-oplock2
 ^samba3.smb2.replay.replay-dhv2-oplock3
+^samba3.smb2.replay.replay-dhv2-lease1
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
 ^samba3.smb2.lock.*replay
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index 741fa4e..050398e 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -759,6 +759,144 @@ done:
 }
 
 /**
+ * Test durablity v2 create replay detection on single channel.
+ * Variant with leases instead of oplocks:
+ * - open a file with a rh lease
+ * - upgrade to a rwh lease with a second create
+ * - replay the first create.
+ *   ==> it gets back the upgraded lease level
+ */
+static bool test_replay_dhv2_lease1(struct torture_context *tctx,
+				    struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h1;
+	struct smb2_handle *h1 = NULL;
+	struct smb2_handle _h2;
+	struct smb2_handle *h2 = NULL;
+	struct smb2_create io1, io2, ref1;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay2_lease1.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls1, ls2;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease "
+			      "on Single Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h1);
+	CHECK_VAL(break_info.count, 0);
+
+	lease_key = random();
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	ref1 = io1;
+	_h1 = io1.out.file.handle;
+	h1 = &_h1;
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io1.out.durable_open, false);
+	CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key);
+	if (share_is_so) {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("R"));
+		CHECK_VAL(io1.out.durable_open_v2, false);
+		CHECK_VAL(io1.out.timeout, 0);
+	} else {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("RH"));
+		CHECK_VAL(io1.out.durable_open_v2, true);
+		CHECK_VAL(io1.out.timeout, io1.in.timeout);
+	}
+
+	/*
+	 * Upgrade the lease to RWH
+	 */
+	smb2_lease_create(&io2, &ls2, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RHW"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = GUID_random(); /* new guid... */
+	io2.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h2 = io2.out.file.handle;
+	h2 = &_h2;
+
+	/*
+	 * Replay Durable V2 Create on single channel.
+	 * We get the io from open #1 but with the
+	 * upgraded lease.
+	 */
+
+	/* adapt expected lease in response */
+	if (!share_is_so) {
+		ref1.out.lease_response.lease_state =
+			smb2_util_lease_state("RHW");
+	}
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io1);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATE_OUT(&io1, &ref1);
+	CHECK_VAL(break_info.count, 0);
+
+done:
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+
+	if (h1 != NULL) {
+		smb2_util_close(tree, *h1);
+	}
+	if (h2 != NULL) {
+		smb2_util_close(tree, *h2);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
  * Test Durablity V2 Create Replay Detection on Multi Channel
  */
 static bool test_replay3(struct torture_context *tctx, struct smb2_tree *tree1)
@@ -1296,6 +1434,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock1", test_replay_dhv2_oplock1);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock2", test_replay_dhv2_oplock2);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock3", test_replay_dhv2_oplock3);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease1",  test_replay_dhv2_lease1);
 	torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
 	torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
 	torture_suite_add_1smb2_test(suite, "replay5", test_replay5);
-- 
2.5.0


From 7233d35e441aa63239710581a1ef342d762bcf77 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Mon, 29 Feb 2016 19:04:32 +0100
Subject: [PATCH 05/15] torture:smb2: add smb2.replay.replay-dhv2-lease2

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            |   1 +
 source4/torture/smb2/replay.c | 152 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 153 insertions(+)

diff --git a/selftest/knownfail b/selftest/knownfail
index 3a61c1d..abca6a9 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -208,6 +208,7 @@
 ^samba3.smb2.replay.replay-dhv2-oplock2
 ^samba3.smb2.replay.replay-dhv2-oplock3
 ^samba3.smb2.replay.replay-dhv2-lease1
+^samba3.smb2.replay.replay-dhv2-lease2
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
 ^samba3.smb2.lock.*replay
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index 050398e..600a8a6 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -897,6 +897,157 @@ done:
 }
 
 /**
+ * Test durablity v2 create replay detection on single channel.
+ * Variant with leases instead of oplocks, where the
+ * replay does not specify the original lease level but
+ * just a "R" lease. This still gives the upgraded lease
+ * level in the reply.
+ * - open a file with a rh lease
+ * - upgrade to a rwh lease with a second create
+ * - replay the first create.
+ *   ==> it gets back the upgraded lease level
+ */
+static bool test_replay_dhv2_lease2(struct torture_context *tctx,
+				    struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h1;
+	struct smb2_handle *h1 = NULL;
+	struct smb2_handle _h2;
+	struct smb2_handle *h2 = NULL;
+	struct smb2_create io1, io2, ref1;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay2_lease2.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls1, ls2;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease "
+			      "on Single Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h1);
+	CHECK_VAL(break_info.count, 0);
+
+	lease_key = random();
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io1.out.durable_open, false);
+	CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key);
+	if (share_is_so) {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("R"));
+		CHECK_VAL(io1.out.durable_open_v2, false);
+		CHECK_VAL(io1.out.timeout, 0);
+	} else {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("RH"));
+		CHECK_VAL(io1.out.durable_open_v2, true);
+		CHECK_VAL(io1.out.timeout, io1.in.timeout);
+	}
+	ref1 = io1;
+	_h1 = io1.out.file.handle;
+	h1 = &_h1;
+
+	/*
+	 * Upgrade the lease to RWH
+	 */
+	smb2_lease_create(&io2, &ls2, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RHW"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = GUID_random(); /* new guid... */
+	io2.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h2 = io2.out.file.handle;
+	h2 = &_h2;
+
+	/*
+	 * Replay Durable V2 Create on single channel.
+	 * Changing the requested lease level to "R"
+	 * does not change the response:
+	 * We get the reply from open #1 but with the
+	 * upgraded lease.
+	 */
+
+	/* adapt the expected response */
+	if (!share_is_so) {
+		ref1.out.lease_response.lease_state =
+					smb2_util_lease_state("RHW");
+	}
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("R"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io1);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATE_OUT(&io1, &ref1);
+	CHECK_VAL(break_info.count, 0);
+
+done:
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+
+	if (h1 != NULL) {
+		smb2_util_close(tree, *h1);
+	}
+	if (h2 != NULL) {
+		smb2_util_close(tree, *h2);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
  * Test Durablity V2 Create Replay Detection on Multi Channel
  */
 static bool test_replay3(struct torture_context *tctx, struct smb2_tree *tree1)
@@ -1435,6 +1586,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock2", test_replay_dhv2_oplock2);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock3", test_replay_dhv2_oplock3);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease1",  test_replay_dhv2_lease1);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease2",  test_replay_dhv2_lease2);
 	torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
 	torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
 	torture_suite_add_1smb2_test(suite, "replay5", test_replay5);
-- 
2.5.0


From 481c577afac4f581d2a50ea26988f7036c5d6ebb Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Tue, 1 Mar 2016 23:03:50 +0100
Subject: [PATCH 06/15] torture:smb2: add smb2.replay.replay-dhv2-lease-oplock

Open with a lease and replay with an oplock.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            |   1 +
 source4/torture/smb2/replay.c | 145 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 146 insertions(+)

diff --git a/selftest/knownfail b/selftest/knownfail
index abca6a9..dab4dbe 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -209,6 +209,7 @@
 ^samba3.smb2.replay.replay-dhv2-oplock3
 ^samba3.smb2.replay.replay-dhv2-lease1
 ^samba3.smb2.replay.replay-dhv2-lease2
+^samba3.smb2.replay.replay-dhv2-lease-oplock
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
 ^samba3.smb2.lock.*replay
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index 600a8a6..16d97bc 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -1048,6 +1048,150 @@ done:
 }
 
 /**
+ * Test durablity v2 create replay detection on single channel.
+ * Do the original create with a lease, and do the replay
+ * with an oplock.
+ */
+static bool test_replay_dhv2_lease_oplock(struct torture_context *tctx,
+					  struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h1;
+	struct smb2_handle *h1 = NULL;
+	struct smb2_handle _h2;
+	struct smb2_handle *h2 = NULL;
+	struct smb2_create io1, io2, ref1;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay2_lease1.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls1, ls2;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease "
+			      "on Single Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h1);
+	CHECK_VAL(break_info.count, 0);
+
+	lease_key = random();
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	ref1 = io1;
+	_h1 = io1.out.file.handle;
+	h1 = &_h1;
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io1.out.durable_open, false);
+	CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key);
+	if (share_is_so) {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("R"));
+		CHECK_VAL(io1.out.durable_open_v2, false);
+		CHECK_VAL(io1.out.timeout, 0);
+	} else {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("RH"));
+		CHECK_VAL(io1.out.durable_open_v2, true);
+		CHECK_VAL(io1.out.timeout, io1.in.timeout);
+	}
+
+	/*
+	 * Upgrade the lease to RWH
+	 */
+	smb2_lease_create(&io2, &ls2, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RHW"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = GUID_random(); /* new guid... */
+	io2.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h2 = io2.out.file.handle;
+	h2 = &_h2;
+
+	/*
+	 * Replay Durable V2 Create on single channel.
+	 * We get the io from open #1 but with the
+	 * upgraded lease.
+	 */
+
+	smb2_oplock_create_share(&io2, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = create_guid;
+	io2.in.timeout = UINT32_MAX;
+
+	/* adapt expected lease in response */
+	if (!share_is_so) {
+		ref1.out.lease_response.lease_state =
+			smb2_util_lease_state("RHW");
+	}
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io1);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATE_OUT(&io1, &ref1);
+	CHECK_VAL(break_info.count, 0);
+
+done:
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+
+	if (h1 != NULL) {
+		smb2_util_close(tree, *h1);
+	}
+	if (h2 != NULL) {
+		smb2_util_close(tree, *h2);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
  * Test Durablity V2 Create Replay Detection on Multi Channel
  */
 static bool test_replay3(struct torture_context *tctx, struct smb2_tree *tree1)
@@ -1587,6 +1731,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock3", test_replay_dhv2_oplock3);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease1",  test_replay_dhv2_lease1);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease2",  test_replay_dhv2_lease2);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease-oplock",  test_replay_dhv2_lease_oplock);
 	torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
 	torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
 	torture_suite_add_1smb2_test(suite, "replay5", test_replay5);
-- 
2.5.0


From 6de4a30d231e0b4d08588cc1c04131bfd0547526 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Wed, 2 Mar 2016 01:38:24 +0100
Subject: [PATCH 07/15] torture:smb2: add smb2.replay.replay-oplock-lease

create with an oplock, and replay with a lease.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            |   1 +
 source4/torture/smb2/replay.c | 105 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/selftest/knownfail b/selftest/knownfail
index dab4dbe..3796ac4 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -207,6 +207,7 @@
 ^samba3.smb2.replay.replay-dhv2-oplock1
 ^samba3.smb2.replay.replay-dhv2-oplock2
 ^samba3.smb2.replay.replay-dhv2-oplock3
+^samba3.smb2.replay.replay-dhv2-oplock-lease
 ^samba3.smb2.replay.replay-dhv2-lease1
 ^samba3.smb2.replay.replay-dhv2-lease2
 ^samba3.smb2.replay.replay-dhv2-lease-oplock
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index 16d97bc..d541f7a 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -759,6 +759,110 @@ done:
 }
 
 /**
+ * Test Durablity V2 Create Replay Detection on Single Channel.
+ * Create with an oplock, and replay with a lease.
+ */
+static bool test_replay_dhv2_oplock_lease(struct torture_context *tctx,
+					  struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h;
+	struct smb2_handle *h = NULL;
+	struct smb2_create io;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay_dhv2_oplock1.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 on Single "
+			      "Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h);
+	CHECK_VAL(break_info.count, 0);
+
+	smb2_oplock_create_share(&io, fname,
+			smb2_util_share_access(""),
+			smb2_util_oplock_level("b"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h = io.out.file.handle;
+	h = &_h;
+	CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io.out.durable_open, false);
+	if (share_is_so) {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("s"));
+		CHECK_VAL(io.out.durable_open_v2, false);
+		CHECK_VAL(io.out.timeout, 0);
+	} else {
+		CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+		CHECK_VAL(io.out.durable_open_v2, true);
+		CHECK_VAL(io.out.timeout, io.in.timeout);
+	}
+
+	/*
+	 * Replay Durable V2 Create on single channel
+	 * but replay it with a lease instead of an oplock.
+	 */
+	lease_key = random();
+	smb2_lease_create(&io, &ls, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io.in.durable_open = false;
+	io.in.durable_open_v2 = true;
+	io.in.persistent_open = false;
+	io.in.create_guid = create_guid;
+	io.in.timeout = UINT32_MAX;
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED);
+
+done:
+	if (h != NULL) {
+		smb2_util_close(tree, *h);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+
+/**
  * Test durablity v2 create replay detection on single channel.
  * Variant with leases instead of oplocks:
  * - open a file with a rh lease
@@ -1729,6 +1833,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock1", test_replay_dhv2_oplock1);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock2", test_replay_dhv2_oplock2);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock3", test_replay_dhv2_oplock3);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock-lease", test_replay_dhv2_oplock_lease);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease1",  test_replay_dhv2_lease1);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease2",  test_replay_dhv2_lease2);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease-oplock",  test_replay_dhv2_lease_oplock);
-- 
2.5.0


From 6fdbc97ad24d3fc891edcdf7bfb3b58df34332a3 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Wed, 2 Mar 2016 20:45:16 +0100
Subject: [PATCH 08/15] torture:smb2: add smb2.replay.replay-dhv2-lease3

create with a lease, and replay with lease
with a different lease key.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail            |   1 +
 source4/torture/smb2/replay.c | 134 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 135 insertions(+)

diff --git a/selftest/knownfail b/selftest/knownfail
index 3796ac4..34d1f2d 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -210,6 +210,7 @@
 ^samba3.smb2.replay.replay-dhv2-oplock-lease
 ^samba3.smb2.replay.replay-dhv2-lease1
 ^samba3.smb2.replay.replay-dhv2-lease2
+^samba3.smb2.replay.replay-dhv2-lease3
 ^samba3.smb2.replay.replay-dhv2-lease-oplock
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
diff --git a/source4/torture/smb2/replay.c b/source4/torture/smb2/replay.c
index d541f7a..c32533b 100644
--- a/source4/torture/smb2/replay.c
+++ b/source4/torture/smb2/replay.c
@@ -1153,6 +1153,139 @@ done:
 
 /**
  * Test durablity v2 create replay detection on single channel.
+ * create with a lease, and replay with a different lease key
+ */
+static bool test_replay_dhv2_lease3(struct torture_context *tctx,
+				    struct smb2_tree *tree)
+{
+	NTSTATUS status;
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_handle _h1;
+	struct smb2_handle *h1 = NULL;
+	struct smb2_handle _h2;
+	struct smb2_handle *h2 = NULL;
+	struct smb2_create io1, io2;
+	struct GUID create_guid = GUID_random();
+	bool ret = true;
+	const char *fname = BASEDIR "\\replay2_lease2.dat";
+	struct smb2_transport *transport = tree->session->transport;
+	uint32_t share_capabilities;
+	bool share_is_so;
+	uint32_t server_capabilities;
+	struct smb2_lease ls1, ls2;
+	uint64_t lease_key;
+
+	if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+		torture_skip(tctx, "SMB 3.X Dialect family required for "
+				   "replay tests\n");
+	}
+
+	server_capabilities = smb2cli_conn_server_capabilities(transport->conn);
+	if (!(server_capabilities & SMB2_CAP_LEASING)) {
+		torture_skip(tctx, "leases are not supported");
+	}
+
+	share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
+	share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT;
+
+	ZERO_STRUCT(break_info);
+	break_info.tctx = tctx;
+	tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+	tree->session->transport->oplock.private_data = tree;
+
+	torture_comment(tctx, "Replay of DurableHandleReqV2 with Lease "
+			      "on Single Channel\n");
+	smb2_util_unlink(tree, fname);
+	status = torture_smb2_testdir(tree, BASEDIR, &_h1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	smb2_util_close(tree, _h1);
+	CHECK_VAL(break_info.count, 0);
+
+	lease_key = random();
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_VAL(io1.out.durable_open, false);
+	CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease_key);
+	CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease_key);
+	if (share_is_so) {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("R"));
+		CHECK_VAL(io1.out.durable_open_v2, false);
+		CHECK_VAL(io1.out.timeout, 0);
+	} else {
+		CHECK_VAL(io1.out.lease_response.lease_state,
+			  smb2_util_lease_state("RH"));
+		CHECK_VAL(io1.out.durable_open_v2, true);
+		CHECK_VAL(io1.out.timeout, io1.in.timeout);
+	}
+	_h1 = io1.out.file.handle;
+	h1 = &_h1;
+
+	/*
+	 * Upgrade the lease to RWH
+	 */
+	smb2_lease_create(&io2, &ls2, false /* dir */, fname,
+			lease_key, smb2_util_lease_state("RHW"));
+	io2.in.durable_open = false;
+	io2.in.durable_open_v2 = true;
+	io2.in.persistent_open = false;
+	io2.in.create_guid = GUID_random(); /* new guid... */
+	io2.in.timeout = UINT32_MAX;
+
+	status = smb2_create(tree, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	_h2 = io2.out.file.handle;
+	h2 = &_h2;
+
+	/*
+	 * Replay Durable V2 Create on single channel.
+	 * use a different lease key.
+	 */
+
+	smb2_lease_create(&io1, &ls1, false /* dir */, fname,
+			random() /* lease key */,
+			smb2_util_lease_state("RH"));
+	io1.in.durable_open = false;
+	io1.in.durable_open_v2 = true;
+	io1.in.persistent_open = false;
+	io1.in.create_guid = create_guid;
+	io1.in.timeout = UINT32_MAX;
+
+	smb2cli_session_start_replay(tree->session->smbXcli);
+	status = smb2_create(tree, mem_ctx, &io1);
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+	CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED);
+
+done:
+	smb2cli_session_stop_replay(tree->session->smbXcli);
+
+	if (h1 != NULL) {
+		smb2_util_close(tree, *h1);
+	}
+	if (h2 != NULL) {
+		smb2_util_close(tree, *h2);
+	}
+	smb2_deltree(tree, BASEDIR);
+
+	talloc_free(tree);
+	talloc_free(mem_ctx);
+
+	return ret;
+}
+
+/**
+ * Test durablity v2 create replay detection on single channel.
  * Do the original create with a lease, and do the replay
  * with an oplock.
  */
@@ -1836,6 +1969,7 @@ struct torture_suite *torture_smb2_replay_init(void)
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-oplock-lease", test_replay_dhv2_oplock_lease);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease1",  test_replay_dhv2_lease1);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease2",  test_replay_dhv2_lease2);
+	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease3",  test_replay_dhv2_lease3);
 	torture_suite_add_1smb2_test(suite, "replay-dhv2-lease-oplock",  test_replay_dhv2_lease_oplock);
 	torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
 	torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
-- 
2.5.0


From 4da569705cf36d85d2a4fa8a5c5e4d6b98c0c83f Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Wed, 24 Feb 2016 15:53:57 +0100
Subject: [PATCH 09/15] smbd:smb2: allow the REPLAY_OPERATION flag for SMB3+
 requests

Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>

Signed-off-by: Michael Adam <obnox at samba.org>
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/smbd/smb2_server.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
index ac922a1..68d637e 100644
--- a/source3/smbd/smb2_server.c
+++ b/source3/smbd/smb2_server.c
@@ -2225,6 +2225,9 @@ NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
 	if (opcode == SMB2_OP_CANCEL) {
 		allowed_flags |= SMB2_HDR_FLAG_ASYNC;
 	}
+	if (xconn->protocol >= PROTOCOL_SMB2_22) {
+		allowed_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION;
+	}
 	if ((flags & ~allowed_flags) != 0) {
 		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
 	}
-- 
2.5.0


From ac81b3e9d686b3350f7ad46e7e2f9df8593bb870 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Fri, 26 Feb 2016 13:44:13 +0100
Subject: [PATCH 10/15] librpc:smbXsrv.idl: add flags to smbXsrv_open

These flags reflect the need for and state of the replay cache.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source3/librpc/idl/smbXsrv.idl | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/source3/librpc/idl/smbXsrv.idl b/source3/librpc/idl/smbXsrv.idl
index fe86545..c55ca31 100644
--- a/source3/librpc/idl/smbXsrv.idl
+++ b/source3/librpc/idl/smbXsrv.idl
@@ -404,6 +404,11 @@ interface smbXsrv
 
 	/* open files */
 
+	typedef [public,bitmap8bit] bitmap {
+		SMBXSRV_OPEN_NEED_REPLAY_CACHE		= 0x01,
+		SMBXSRV_OPEN_HAVE_REPLAY_CACHE		= 0x02
+	} smbXsrv_open_flags;
+
 	typedef struct {
 		[ignore] db_record 			*db_rec;
 		server_id				server_id;
@@ -463,6 +468,7 @@ interface smbXsrv
 		NTSTATUS				status;
 		NTTIME					idle_time;
 		[ignore] files_struct			*compat;
+		smbXsrv_open_flags			flags;
 	} smbXsrv_open;
 
 	typedef union {
-- 
2.5.0


From 9257078d43ce93001e6ea9d13dad36a8baa2a17f Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Wed, 24 Feb 2016 00:23:15 +0100
Subject: [PATCH 11/15] smbXsrv:open: maintain a replay cache

This caches a map create_guid -> file_id, so that
a replayed create can find the already created
open again.

This is automatically deleted once the first use
of the file handle is happening (triggered by
the lookup for the file-id).

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source3/smbd/smbXsrv_open.c | 108 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 106 insertions(+), 2 deletions(-)

diff --git a/source3/smbd/smbXsrv_open.c b/source3/smbd/smbXsrv_open.c
index ee35f2d..3c66f42 100644
--- a/source3/smbd/smbXsrv_open.c
+++ b/source3/smbd/smbXsrv_open.c
@@ -34,6 +34,7 @@
 struct smbXsrv_open_table {
 	struct {
 		struct db_context *db_ctx;
+		struct db_context *replay_cache_db_ctx;
 		uint32_t lowest_id;
 		uint32_t highest_id;
 		uint32_t max_opens;
@@ -225,6 +226,11 @@ static NTSTATUS smbXsrv_open_table_init(struct smbXsrv_connection *conn,
 		TALLOC_FREE(table);
 		return NT_STATUS_NO_MEMORY;
 	}
+	table->local.replay_cache_db_ctx = db_open_rbt(table);
+	if (table->local.replay_cache_db_ctx == NULL) {
+		TALLOC_FREE(table);
+		return NT_STATUS_NO_MEMORY;
+	}
 	table->local.lowest_id = lowest_id;
 	table->local.highest_id = highest_id;
 	table->local.max_opens = max_opens;
@@ -928,6 +934,73 @@ uint32_t smbXsrv_open_hash(struct smbXsrv_open *_open)
 	return ret;
 }
 
+static NTSTATUS smbXsrv_open_set_replay_cache(struct smbXsrv_open *op)
+{
+	struct GUID *create_guid;
+	struct GUID_txt_buf buf;
+	char *guid_string;
+	struct db_context *db = op->table->local.replay_cache_db_ctx;
+	NTSTATUS status;
+
+	if (!(op->flags & SMBXSRV_OPEN_NEED_REPLAY_CACHE)) {
+		return NT_STATUS_OK;
+	}
+
+	if (op->flags & SMBXSRV_OPEN_HAVE_REPLAY_CACHE) {
+		return NT_STATUS_OK;
+	}
+
+	create_guid = &op->global->create_guid;
+	if (GUID_all_zero(create_guid)) {
+		return NT_STATUS_OK;
+	}
+
+	guid_string = GUID_buf_string(create_guid, &buf);
+	if (guid_string == NULL) {
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	status = dbwrap_store_uint32_bystring(db, guid_string, op->local_id);
+
+	if (NT_STATUS_IS_OK(status)) {
+		op->flags |= SMBXSRV_OPEN_HAVE_REPLAY_CACHE;
+		op->flags &= ~SMBXSRV_OPEN_NEED_REPLAY_CACHE;
+	}
+
+	return status;
+}
+
+static NTSTATUS smbXsrv_open_clear_replay_cache(struct smbXsrv_open *op)
+{
+	struct GUID *create_guid;
+	struct GUID_txt_buf buf;
+	char *guid_string;
+	struct db_context *db = op->table->local.replay_cache_db_ctx;
+	NTSTATUS status;
+
+	if (!(op->flags & SMBXSRV_OPEN_HAVE_REPLAY_CACHE)) {
+		return NT_STATUS_OK;
+	}
+
+	create_guid = &op->global->create_guid;
+	if (GUID_all_zero(create_guid)) {
+		return NT_STATUS_OK;
+	}
+
+	guid_string = GUID_buf_string(create_guid, &buf);
+	if (guid_string == NULL) {
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	status = dbwrap_purge_bystring(db, guid_string);
+
+	if (NT_STATUS_IS_OK(status)) {
+		op->flags &= ~SMBXSRV_OPEN_HAVE_REPLAY_CACHE;
+	}
+
+	return status;
+}
+
 NTSTATUS smbXsrv_open_update(struct smbXsrv_open *op)
 {
 	struct smbXsrv_open_table *table = op->table;
@@ -957,6 +1030,13 @@ NTSTATUS smbXsrv_open_update(struct smbXsrv_open *op)
 		return status;
 	}
 
+	status = smbXsrv_open_set_replay_cache(op);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("smbXsrv_open_set_replay_cache failed: %s\n",
+			nt_errstr(status));
+		return status;
+	}
+
 	if (CHECK_DEBUGLVL(10)) {
 		struct smbXsrv_openB open_blob;
 
@@ -980,8 +1060,14 @@ NTSTATUS smbXsrv_open_close(struct smbXsrv_open *op, NTTIME now)
 	NTSTATUS status;
 	NTSTATUS error = NT_STATUS_OK;
 
+	error = smbXsrv_open_clear_replay_cache(op);
+	if (!NT_STATUS_IS_OK(error)) {
+		DBG_ERR("smbXsrv_open_clear_replay_cache failed: %s\n",
+			nt_errstr(error));
+	}
+
 	if (op->table == NULL) {
-		return NT_STATUS_OK;
+		return error;
 	}
 
 	table = op->table;
@@ -1157,6 +1243,7 @@ NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
 	uint64_t local_zeros = volatile_id & 0xFFFFFFFF00000000LLU;
 	uint32_t global_id = persistent_id & UINT32_MAX;
 	uint64_t global_zeros = persistent_id & 0xFFFFFFFF00000000LLU;
+	NTSTATUS status;
 
 	if (local_zeros != 0) {
 		return NT_STATUS_FILE_CLOSED;
@@ -1170,7 +1257,24 @@ NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
 		return NT_STATUS_FILE_CLOSED;
 	}
 
-	return smbXsrv_open_local_lookup(table, local_id, global_id, now, _open);
+	status = smbXsrv_open_local_lookup(table, local_id, global_id, now,
+					   _open);
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
+
+	/*
+	 * Clear the replay cache for this create_guid if it exists:
+	 * This is based on the assumption that this lookup will be
+	 * triggered by a client request using the file-id for lookup.
+	 * Hence the client has proven that it has in fact seen the
+	 * reply to its initial create call. So subsequent create replays
+	 * should be treated as invalid. Hence the index for create_guid
+	 * lookup needs to be removed.
+	 */
+	status = smbXsrv_open_clear_replay_cache(*_open);
+
+	return status;
 }
 
 NTSTATUS smb2srv_open_recreate(struct smbXsrv_connection *conn,
-- 
2.5.0


From 3a5b51ddf266ba9449c4496d0266df0da5182c50 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Fri, 26 Feb 2016 13:53:25 +0100
Subject: [PATCH 12/15] smb2:create: create replay cache when request has a
 create_guid

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source3/smbd/smb2_create.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
index 7917d42..6f01c39 100644
--- a/source3/smbd/smb2_create.c
+++ b/source3/smbd/smb2_create.c
@@ -668,6 +668,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 		struct smb2_lease lease;
 		struct smb2_lease *lease_ptr = NULL;
 		ssize_t lease_len = -1;
+		bool need_replay_cache = false;
 #if 0
 		struct smb2_create_blob *svhdx = NULL;
 #endif
@@ -804,6 +805,12 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 			update_open = true;
 
 			/*
+			 * And we need to create a cache for replaying the
+			 * create.
+			 */
+			need_replay_cache = true;
+
+			/*
 			 * durable handle v2 request processed below
 			 */
 			durable_requested = true;
@@ -1154,6 +1161,9 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 
 		if (update_open) {
 			op->global->create_guid = _create_guid;
+			if (need_replay_cache) {
+				op->flags |= SMBXSRV_OPEN_NEED_REPLAY_CACHE;
+			}
 
 			status = smbXsrv_open_update(op);
 			DEBUG(10, ("smb2_create_send: smbXsrv_open_update "
-- 
2.5.0


From 1a256993a065d67aeb8303e56ea4794aeda311d8 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Sat, 27 Feb 2016 03:23:27 +0100
Subject: [PATCH 13/15] smbXsrv:open: add smb2srv_open_lookup_replay_cache()

A function to find an open from the replay cache,
based on the create_guid handed in.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source3/smbd/globals.h      |  4 ++++
 source3/smbd/smbXsrv_open.c | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h
index 1ca1389..c843f5a 100644
--- a/source3/smbd/globals.h
+++ b/source3/smbd/globals.h
@@ -641,6 +641,10 @@ NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
 			     uint64_t volatile_id,
 			     NTTIME now,
 			     struct smbXsrv_open **_open);
+NTSTATUS smb2srv_open_lookup_replay_cache(struct smbXsrv_connection *conn,
+					  const struct GUID *create_guid,
+					  NTTIME now,
+					  struct smbXsrv_open **_open);
 NTSTATUS smb2srv_open_recreate(struct smbXsrv_connection *conn,
 			       struct auth_session_info *session_info,
 			       uint64_t persistent_id,
diff --git a/source3/smbd/smbXsrv_open.c b/source3/smbd/smbXsrv_open.c
index 3c66f42..50e0280 100644
--- a/source3/smbd/smbXsrv_open.c
+++ b/source3/smbd/smbXsrv_open.c
@@ -1277,6 +1277,44 @@ NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
 	return status;
 }
 
+NTSTATUS smb2srv_open_lookup_replay_cache(struct smbXsrv_connection *conn,
+					  const struct GUID *create_guid,
+					  NTTIME now, /* TODO: needed ? */
+					  struct smbXsrv_open **_open)
+{
+	NTSTATUS status;
+	char *guid_string;
+	struct GUID_txt_buf buf;
+	uint32_t local_id = 0;
+	struct smbXsrv_open_table *table = conn->client->open_table;
+	struct db_context *db = table->local.replay_cache_db_ctx;
+
+	if (GUID_all_zero(create_guid)) {
+		return NT_STATUS_NOT_FOUND;
+	}
+
+	guid_string = GUID_buf_string(create_guid, &buf);
+	if (guid_string == NULL) {
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	status = dbwrap_fetch_uint32_bystring(db, guid_string, &local_id);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("failed to fetch local_id from replay cache: %s\n",
+			nt_errstr(status));
+		return status;
+	}
+
+	status = smbXsrv_open_local_lookup(table, local_id, 0, /* global_id */
+					   now, _open);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("smbXsrv_open_local_lookup failed for local_id %u\n",
+			(unsigned)local_id);
+	}
+
+	return status;
+}
+
 NTSTATUS smb2srv_open_recreate(struct smbXsrv_connection *conn,
 			       struct auth_session_info *session_info,
 			       uint64_t persistent_id,
-- 
2.5.0


From 827ab8f24171dc4dfe30e3a23288a68781dc0057 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Mon, 29 Feb 2016 02:11:26 +0100
Subject: [PATCH 14/15] smbXsrv.idl: add create_action to smbXsrv_open

Needed for create replay.

Signed-off-by: Michael Adam <obnox at samba.org>
---
 source3/librpc/idl/smbXsrv.idl | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source3/librpc/idl/smbXsrv.idl b/source3/librpc/idl/smbXsrv.idl
index c55ca31..4c6895a 100644
--- a/source3/librpc/idl/smbXsrv.idl
+++ b/source3/librpc/idl/smbXsrv.idl
@@ -469,6 +469,7 @@ interface smbXsrv
 		NTTIME					idle_time;
 		[ignore] files_struct			*compat;
 		smbXsrv_open_flags			flags;
+		uint32					create_action;
 	} smbXsrv_open;
 
 	typedef union {
-- 
2.5.0


From f856e05c57be183ba40f0f4e5522f8369be9b46d Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Sun, 28 Feb 2016 02:32:36 +0100
Subject: [PATCH 15/15] smbd:smb2: implement create replay

Signed-off-by: Michael Adam <obnox at samba.org>
---
 selftest/knownfail         | 10 -----
 source3/smbd/smb2_create.c | 94 +++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 88 insertions(+), 16 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 34d1f2d..1ac99d4 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -202,16 +202,6 @@
 ^samba3.smb2.setinfo.setinfo
 ^samba3.smb2.session.*reauth5 # some special anonymous checks?
 ^samba3.smb2.compound.interim2 # wrong return code (STATUS_CANCELLED)
-^samba3.smb2.replay.replay-commands
-^samba3.smb2.replay.replay-regular
-^samba3.smb2.replay.replay-dhv2-oplock1
-^samba3.smb2.replay.replay-dhv2-oplock2
-^samba3.smb2.replay.replay-dhv2-oplock3
-^samba3.smb2.replay.replay-dhv2-oplock-lease
-^samba3.smb2.replay.replay-dhv2-lease1
-^samba3.smb2.replay.replay-dhv2-lease2
-^samba3.smb2.replay.replay-dhv2-lease3
-^samba3.smb2.replay.replay-dhv2-lease-oplock
 ^samba3.smb2.replay.replay3
 ^samba3.smb2.replay.replay4
 ^samba3.smb2.lock.*replay
diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
index 6f01c39..34e07ec 100644
--- a/source3/smbd/smb2_create.c
+++ b/source3/smbd/smb2_create.c
@@ -475,6 +475,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 	struct smb2_create_blob *dh2q = NULL;
 	struct smb2_create_blob *rqls = NULL;
 	struct smbXsrv_open *op = NULL;
+	bool replay_operation = false;
 
 	if(lp_fake_oplocks(SNUM(smb2req->tcon->compat))) {
 		requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
@@ -779,6 +780,8 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 			const uint8_t *p = dh2q->data.data;
 			uint32_t durable_v2_timeout = 0;
 			DATA_BLOB create_guid_blob;
+			const uint8_t *hdr;
+			uint32_t flags;
 
 			if (dh2q->data.length != 32) {
 				tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
@@ -823,6 +826,38 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 				 */
 				durable_timeout_msec = (60*1000);
 			}
+
+			/*
+			 * Check for replay operation.
+			 * Only consider it when we have dh2q.
+			 * If we do not have a replay operation, verify that
+			 * the create_guid is not cached for replay.
+			 */
+			hdr = SMBD_SMB2_IN_HDR_PTR(smb2req);
+			flags = IVAL(hdr, SMB2_HDR_FLAGS);
+			replay_operation =
+				!!(flags & SMB2_HDR_FLAG_REPLAY_OPERATION);
+
+			status = smb2srv_open_lookup_replay_cache(
+					smb2req->xconn, create_guid,
+					0 /* now */, &op);
+
+			if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+				replay_operation = false;
+			} else if (tevent_req_nterror(req, status)) {
+				DBG_WARNING("smb2srv_open_lookup_replay_cache "
+					    "failed: %s\n", nt_errstr(status));
+				return tevent_req_post(req, ev);
+			} else if (!replay_operation) {
+				/*
+				 * If a create without replay operation flag
+				 * is sent but with a create_guid that is
+				 * currently in the replay cache -- fail.
+				 */
+				status = NT_STATUS_DUPLICATE_OBJECTID;
+				(void)tevent_req_nterror(req, status);
+				return tevent_req_post(req, ev);
+			}
 		}
 
 		if (dhnc) {
@@ -918,6 +953,32 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 				DEBUG(10, ("v2 lease key only for SMB3\n"));
 				lease_ptr = NULL;
 			}
+
+			/*
+			 * Replay with a lease is only allowed if the
+			 * established open carries a lease with the
+			 * same lease key.
+			 */
+			if (replay_operation) {
+				struct smb2_lease *op_ls =
+						&op->compat->lease->lease;
+				int op_oplock = op->compat->oplock_type;
+
+				if (map_samba_oplock_levels_to_smb2(op_oplock)
+				    != SMB2_OPLOCK_LEVEL_LEASE)
+				{
+					status = NT_STATUS_ACCESS_DENIED;
+					(void)tevent_req_nterror(req, status);
+					return tevent_req_post(req, ev);
+				}
+				if (!smb2_lease_key_equal(&lease.lease_key,
+							  &op_ls->lease_key))
+				{
+					status = NT_STATUS_ACCESS_DENIED;
+					(void)tevent_req_nterror(req, status);
+					return tevent_req_post(req, ev);
+				}
+			}
 		}
 
 		/* these are ignored for SMB2 */
@@ -930,9 +991,15 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 
 		/*
 		 * For the backend file open procedure, there are
-		 * two possible modes: durable_reconnect or not.
+		 * three possible modes: replay operation (in which case
+		 * there is nothing else to do), durable_reconnect or
+		 * new open.
 		 */
-		if (do_durable_reconnect) {
+		if (replay_operation) {
+			result = op->compat;
+			update_open = false;
+			info = op->create_action;
+		} else if (do_durable_reconnect) {
 			DATA_BLOB new_cookie = data_blob_null;
 			NTTIME now = timeval_to_nttime(&smb2req->request_time);
 
@@ -1142,7 +1209,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 			}
 		}
 
-		if (durable_requested &&
+		if (!replay_operation && durable_requested &&
 		    (fsp_lease_type(result) & SMB2_LEASE_HANDLE))
 		{
 			status = SMB_VFS_DURABLE_COOKIE(result,
@@ -1152,7 +1219,8 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 				op->global->backend_cookie = data_blob_null;
 			}
 		}
-		if (op->global->backend_cookie.length > 0) {
+		if (!replay_operation && op->global->backend_cookie.length > 0)
+		{
 			update_open = true;
 
 			op->global->durable = true;
@@ -1189,7 +1257,18 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 			}
 		}
 
-		if (dh2q && op->global->durable) {
+		if (dh2q && op->global->durable &&
+		    /*
+		     * For replay operations, we return the dh2q blob
+		     * in the case of oplocks not based on the state of
+		     * the open, but on whether it could have been granted
+		     * for the request data. In the case of leases instead,
+		     * the state of the open is used...
+		     */
+		    (!replay_operation ||
+		     in_oplock_level == SMB2_OPLOCK_LEVEL_BATCH ||
+		     in_oplock_level == SMB2_OPLOCK_LEVEL_LEASE))
+		{
 			uint8_t p[8] = { 0, };
 			DATA_BLOB blob = data_blob_const(p, sizeof(p));
 			uint32_t durable_v2_response_flags = 0;
@@ -1261,7 +1340,9 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 
 	smb2req->compat_chain_fsp = smb1req->chain_fsp;
 
-	if(lp_fake_oplocks(SNUM(smb2req->tcon->compat))) {
+	if (replay_operation) {
+		state->out_oplock_level = in_oplock_level;
+	} else if (lp_fake_oplocks(SNUM(smb2req->tcon->compat))) {
 		state->out_oplock_level	= in_oplock_level;
 	} else {
 		state->out_oplock_level	= map_samba_oplock_levels_to_smb2(result->oplock_type);
@@ -1273,6 +1354,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 	} else {
 		state->out_create_action = info;
 	}
+	op->create_action = state->out_create_action;
 	state->out_file_attributes = dos_mode(result->conn,
 					   result->fsp_name);
 
-- 
2.5.0

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 198 bytes
Desc: not available
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20160302/6a01c1a2/signature.sig>


More information about the samba-technical mailing list