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

Jeremy Allison jra at samba.org
Wed Mar 2 20:05:29 UTC 2016


On Tue, Mar 01, 2016 at 02:15:33AM +0100, Michael Adam wrote:
> Hi all,
> 
> here is an important step toward multi channel:
> Implementation of create replay.
> 
> - This patchset includes the previously sent
>   patches for dbwrap_purge as the first patches.
> 
> - It then reworks several replay tests that work
>   on a single channel by splitting them up
>   into more easily understandable smaller tests
>   and extending them for better coverage.
> 
> - Next, preparation in smbXsrv_open are done
>   to implement a replay cache to find an open
>   based on the create_guid.
> 
> - finally, these mechanisms are used in smb2_create
>   to implement create replay.
> 
> Review appreciated.

LGTM - with the first (already applied) patches
removed ! Pushed.

> From 77309781475b78cd86df65f11745dbd0793e20eb Mon Sep 17 00:00:00 2001
> From: Michael Adam <obnox at samba.org>
> Date: Thu, 25 Feb 2016 16:02:36 +0100
> Subject: [PATCH 01/16] dbwrap_util: improve a debug message in
>  dbwrap_delete_action()
> 
> Signed-off-by: Michael Adam <obnox at samba.org>
> ---
>  lib/dbwrap/dbwrap_util.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/lib/dbwrap/dbwrap_util.c b/lib/dbwrap/dbwrap_util.c
> index 901ef56..5118fb7 100644
> --- a/lib/dbwrap/dbwrap_util.c
> +++ b/lib/dbwrap/dbwrap_util.c
> @@ -412,7 +412,8 @@ static NTSTATUS dbwrap_delete_action(struct db_context * db, void *private_data)
>  
>  	status = dbwrap_record_delete(rec);
>  	if (!NT_STATUS_IS_OK(status)) {
> -		DEBUG(5, ("delete_rec returned %s\n", nt_errstr(status)));
> +		DBG_INFO("dbwrap_record_delete returned %s\n",
> +			 nt_errstr(status));
>  	}
>  
>  	talloc_free(rec);
> -- 
> 2.5.0
> 
> 
> From 78a563c8ab045918515bcd77179bbc9941f4b221 Mon Sep 17 00:00:00 2001
> From: Michael Adam <obnox at samba.org>
> Date: Thu, 25 Feb 2016 00:56:14 +0100
> Subject: [PATCH 02/16] dbwrap: add dbwrap_purge[_bystring]
> 
> Variants of dbrwap_delete[_bysrting] that treats NOT FOUND
> as success.
> 
> Signed-off-by: Michael Adam <obnox at samba.org>
> ---
>  lib/dbwrap/dbwrap.h      |  2 ++
>  lib/dbwrap/dbwrap_util.c | 17 +++++++++++++++++
>  2 files changed, 19 insertions(+)
> 
> diff --git a/lib/dbwrap/dbwrap.h b/lib/dbwrap/dbwrap.h
> index 5e13a59..2eded04 100644
> --- a/lib/dbwrap/dbwrap.h
> +++ b/lib/dbwrap/dbwrap.h
> @@ -94,6 +94,8 @@ const char *dbwrap_name(struct db_context *db);
>  
>  /* The following definitions come from lib/dbwrap_util.c  */
>  
> +NTSTATUS dbwrap_purge(struct db_context *db, TDB_DATA key);
> +NTSTATUS dbwrap_purge_bystring(struct db_context *db, const char *key);
>  NTSTATUS dbwrap_delete_bystring(struct db_context *db, const char *key);
>  NTSTATUS dbwrap_store_bystring(struct db_context *db, const char *key,
>  			       TDB_DATA data, int flags);
> diff --git a/lib/dbwrap/dbwrap_util.c b/lib/dbwrap/dbwrap_util.c
> index 5118fb7..22f910d 100644
> --- a/lib/dbwrap/dbwrap_util.c
> +++ b/lib/dbwrap/dbwrap_util.c
> @@ -528,6 +528,23 @@ NTSTATUS dbwrap_trans_traverse(struct db_context *db,
>  	return dbwrap_trans_do(db, dbwrap_trans_traverse_action, &ctx);
>  }
>  
> +NTSTATUS dbwrap_purge(struct db_context *db, TDB_DATA key)
> +{
> +	NTSTATUS status;
> +
> +	status = dbwrap_delete(db, key);
> +	if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
> +		status = NT_STATUS_OK;
> +	}
> +
> +	return status;
> +}
> +
> +NTSTATUS dbwrap_purge_bystring(struct db_context *db, const char *key)
> +{
> +	return dbwrap_purge(db, string_term_tdb_data(key));
> +}
> +
>  NTSTATUS dbwrap_delete_bystring(struct db_context *db, const char *key)
>  {
>  	return dbwrap_delete(db, string_term_tdb_data(key));
> -- 
> 2.5.0
> 
> 
> From 8225f8a907d5a0f7b730c30d12aeaaa4e71a3b8f Mon Sep 17 00:00:00 2001
> From: Michael Adam <obnox at samba.org>
> Date: Thu, 25 Feb 2016 00:58:50 +0100
> Subject: [PATCH 03/16] s3:registry: use dbwrap_purge_bystring instead of
>  dbwrap_delete_bystring
> 
> where appropriate
> 
> Signed-off-by: Michael Adam <obnox at samba.org>
> ---
>  source3/registry/reg_backend_db.c | 7 +------
>  1 file changed, 1 insertion(+), 6 deletions(-)
> 
> diff --git a/source3/registry/reg_backend_db.c b/source3/registry/reg_backend_db.c
> index 7b3391d..bdfe7d2 100644
> --- a/source3/registry/reg_backend_db.c
> +++ b/source3/registry/reg_backend_db.c
> @@ -966,12 +966,7 @@ static WERROR regdb_delete_key_with_prefix(struct db_context *db,
>  		goto done;
>  	}
>  
> -	werr = ntstatus_to_werror(dbwrap_delete_bystring(db, path));
> -
> -	/* treat "not found" as ok */
> -	if (W_ERROR_EQUAL(werr, WERR_NOT_FOUND)) {
> -		werr = WERR_OK;
> -	}
> +	werr = ntstatus_to_werror(dbwrap_purge_bystring(db, path));
>  
>  done:
>  	talloc_free(mem_ctx);
> -- 
> 2.5.0
> 
> 
> From 49c2870de5b40fec80c885b4a8a5bd2f4ad41b51 Mon Sep 17 00:00:00 2001
> From: Michael Adam <obnox at samba.org>
> Date: Thu, 25 Feb 2016 16:15:04 +0100
> Subject: [PATCH 04/16] netlogon_creds_cli: use dbwrap_purge instead of
>  dbwrap_delete where appropriate
> 
> Signed-off-by: Michael Adam <obnox at samba.org>
> ---
>  libcli/auth/netlogon_creds_cli.c | 14 ++++----------
>  1 file changed, 4 insertions(+), 10 deletions(-)
> 
> diff --git a/libcli/auth/netlogon_creds_cli.c b/libcli/auth/netlogon_creds_cli.c
> index 7c867cf..38b1351 100644
> --- a/libcli/auth/netlogon_creds_cli.c
> +++ b/libcli/auth/netlogon_creds_cli.c
> @@ -1031,11 +1031,8 @@ struct tevent_req *netlogon_creds_cli_auth_send(TALLOC_CTX *mem_ctx,
>  		return req;
>  	}
>  
> -	status = dbwrap_delete(state->context->db.ctx,
> -			       state->context->db.key_data);
> -	if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
> -		status = NT_STATUS_OK;
> -	}
> +	status = dbwrap_purge(state->context->db.ctx,
> +			      state->context->db.key_data);
>  	if (tevent_req_nterror(req, status)) {
>  		return tevent_req_post(req, ev);
>  	}
> @@ -1065,11 +1062,8 @@ static void netlogon_creds_cli_auth_locked(struct tevent_req *subreq)
>  	}
>  	state->locked_state->is_glocked = true;
>  
> -	status = dbwrap_delete(state->context->db.ctx,
> -			       state->context->db.key_data);
> -	if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
> -		status = NT_STATUS_OK;
> -	}
> +	status = dbwrap_purge(state->context->db.ctx,
> +			      state->context->db.key_data);
>  	if (tevent_req_nterror(req, status)) {
>  		return;
>  	}
> -- 
> 2.5.0
> 
> 
> From 2c315991301baf650a83875b4df76f4068ddbe9b 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 05/16] 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..e6944a9 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 26c32d1..91ac6e6 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) {
> @@ -989,7 +989,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 203a6a77c20a6569f40170e0df5010e7e35d2479 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 06/16] 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 e6944a9..bd3151c 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 91ac6e6..e79d3c9 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);
>  	}
> @@ -990,7 +1279,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 21245d31a09afe386c4cee1465ce4bdc685cf33a 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 07/16] 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 e79d3c9..123e558 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 d3002a96ace9acfa5bb97eb247bb649cf88a6709 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 08/16] torture:smb2: add replay_dhv2_lease1 test
> 
> 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 bd3151c..7dc64e4 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 123e558..a1728cb 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)
> @@ -1288,6 +1426,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 d9ce73df32ca09d257ff444643da2a504439398f 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 09/16] torture:smb2: add replay2_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 7dc64e4..16162e3 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 a1728cb..cf31750 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)
> @@ -1427,6 +1578,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 d0d6d5231c3dfe72d29b0bd825352c33717d44f5 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 10/16] 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 91e342d056d7dddd0c312230aa2b129e16bcfeaf 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 11/16] 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 ecb1e3389efbcdeee3a46c3f5a741d438f60fe98 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 12/16] 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 2c77dec2d3c74f610f47dabc660afd82cd410b61 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 13/16] 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 f7ce0e306a322b6ccfe5134af87d304ec7ac06e5 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 14/16] 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 d46635307d596ef5456152df8ac0fc5f7d4c8f86 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 15/16] 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 fc80988d4b46d23d07bf3248d2b4d81df9ad59ee 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 16/16] smbd:smb2: implement create replay
> 
> Signed-off-by: Michael Adam <obnox at samba.org>
> ---
>  selftest/knownfail         |  8 ------
>  source3/smbd/smb2_create.c | 68 ++++++++++++++++++++++++++++++++++++++++++----
>  2 files changed, 62 insertions(+), 14 deletions(-)
> 
> diff --git a/selftest/knownfail b/selftest/knownfail
> index 16162e3..9326083 100644
> --- a/selftest/knownfail
> +++ b/selftest/knownfail
> @@ -202,14 +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_lease1
> -^samba3.smb2.replay.replay_dhv2_lease2
> -^samba3.smb2.replay.replay3
>  ^samba3.smb2.replay.replay4
>  ^samba3.smb2.lock.*replay
>  ^samba3.raw.session.*reauth2 # maybe fix this?
> diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
> index 6f01c39..bd64df7 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) {
> @@ -930,9 +965,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 +1183,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 +1193,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 +1231,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 +1314,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 +1328,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
> 






More information about the samba-technical mailing list