Current SMB2 leases patchset (WIP).

Jeremy Allison jra at samba.org
Tue Oct 28 21:30:16 MDT 2014


On Tue, Oct 28, 2014 at 04:14:13PM -0700, Jeremy Allison wrote:
> 
> Scratch that previous patchset/wip. I missed a couple of UINT16_MAX -> UINT32_MAX
> changes.
> 
> This is a corrected one with lease_idx now uint32_t.

Discovered the root cause of the oplock problem
that causes the samba3.smb2.oplock test to fail.

It's the code added in:

"s3: leases - oplock break errors"
commit 0626052a4fdfe03f9fb137b9630b5111b129ef7b.

Which I initially put as part of the patchset,
but probably shouldn't have.

Volker told me this is code created whilst he was
at Redmond to get closer to passing the Microsoft
oplock torture tests - and in this case what
they're testing is if we give the correct error
message when a client responds to a break message
when it shouldn't. So the test is failing because
we respond NT_STATUS_INVALID_DEVICE_STATE instead
of what the test expects, NT_STATUS_INVALID_OPLOCK_PROTOCOL.

Big whoop, just not important - we're sending an
error message in the insane client case, and that's
what matters. We can work on passing the MS test
cases later as it's only testing returning a specific
error code when the client does something insane, this
isn't a blocking factor in getting the leases code in.

So here is the patchset minus the "s3: leases - oplock break errors"
change. I'll test tomorrow that it passes a full
make test (late here tonight).

Cheers,

Jeremy
-------------- next part --------------
From e30ad92da7fd54f66bdacbda275ef3ecd6ad78a3 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Mon, 22 Sep 2014 21:21:36 +0200
Subject: [PATCH 01/22] s3: leases: mask off
 SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET.

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 libcli/smb/smb2_lease.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcli/smb/smb2_lease.c b/libcli/smb/smb2_lease.c
index f97f096..6fc26f2 100644
--- a/libcli/smb/smb2_lease.c
+++ b/libcli/smb/smb2_lease.c
@@ -47,6 +47,7 @@ ssize_t smb2_lease_pull(const uint8_t *buf, size_t len,
 	switch (version) {
 	case 1:
 		ZERO_STRUCT(lease->parent_lease_key);
+		lease->lease_flags &= ~SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET;
 		lease->lease_epoch = 0;
 		break;
 	case 2:
-- 
1.9.1


From a577745754e502dcad3bab71b98963a9636f7961 Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Tue, 28 Oct 2014 15:20:26 -0700
Subject: [PATCH 02/22] s3: leases: Change from ndr_pull_struct_blob() to
 ndr_pull_struct_blob_all() so we fail if not all bytes are consumed.

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/locking/share_mode_lock.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source3/locking/share_mode_lock.c b/source3/locking/share_mode_lock.c
index 12f499b..ed0fb91 100644
--- a/source3/locking/share_mode_lock.c
+++ b/source3/locking/share_mode_lock.c
@@ -133,7 +133,7 @@ static struct share_mode_data *parse_share_modes(TALLOC_CTX *mem_ctx,
 	blob.data = dbuf.dptr;
 	blob.length = dbuf.dsize;
 
-	ndr_err = ndr_pull_struct_blob(
+	ndr_err = ndr_pull_struct_blob_all(
 		&blob, d, d, (ndr_pull_flags_fn_t)ndr_pull_share_mode_data);
 	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
 		DEBUG(1, ("ndr_pull_share_mode_lock failed: %s\n",
@@ -473,7 +473,7 @@ static int traverse_fn(struct db_record *rec, void *_state)
 	blob.data = value.dptr;
 	blob.length = value.dsize;
 
-	ndr_err = ndr_pull_struct_blob(
+	ndr_err = ndr_pull_struct_blob_all(
 		&blob, d, d, (ndr_pull_flags_fn_t)ndr_pull_share_mode_data);
 	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
 		DEBUG(1, ("ndr_pull_share_mode_lock failed\n"));
-- 
1.9.1


From d5632e68b7dc5221c9a7801e16f2f0fed156ec94 Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Tue, 28 Oct 2014 15:27:09 -0700
Subject: [PATCH 03/22] s3: leases - convert have_read field to num_read.

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/locking/brlock.c | 125 +++++++++++++++++++++--------------------------
 1 file changed, 57 insertions(+), 68 deletions(-)

diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index 295e147..4ad9a21 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -47,7 +47,7 @@ struct byte_range_lock {
 	struct files_struct *fsp;
 	unsigned int num_locks;
 	bool modified;
-	bool have_read_oplocks;
+	uint32_t num_read_oplocks;
 	struct lock_struct *lock_data;
 	struct db_record *record;
 };
@@ -82,18 +82,18 @@ struct files_struct *brl_fsp(struct byte_range_lock *brl)
 	return brl->fsp;
 }
 
-bool brl_have_read_oplocks(const struct byte_range_lock *brl)
+uint32_t brl_num_read_oplocks(const struct byte_range_lock *brl)
 {
-	return brl->have_read_oplocks;
+	return brl->num_read_oplocks;
 }
 
-void brl_set_have_read_oplocks(struct byte_range_lock *brl,
-			       bool have_read_oplocks)
+void brl_set_num_read_oplocks(struct byte_range_lock *brl,
+			      uint32_t num_read_oplocks)
 {
-	DEBUG(10, ("Setting have_read_oplocks to %s\n",
-		   have_read_oplocks ? "true" : "false"));
+	DEBUG(10, ("Setting have_read_oplocks to %"PRIu32"\n",
+		   num_read_oplocks));
 	SMB_ASSERT(brl->record != NULL); /* otherwise we're readonly */
-	brl->have_read_oplocks = have_read_oplocks;
+	brl->num_read_oplocks = num_read_oplocks;
 	brl->modified = true;
 }
 
@@ -1841,7 +1841,6 @@ int brl_forall(void (*fn)(struct file_id id, struct server_id pid,
 
 static void byte_range_lock_flush(struct byte_range_lock *br_lck)
 {
-	size_t data_len;
 	unsigned i;
 	struct lock_struct *locks = br_lck->lock_data;
 
@@ -1865,15 +1864,7 @@ static void byte_range_lock_flush(struct byte_range_lock *br_lck)
 		}
 	}
 
-	data_len = br_lck->num_locks * sizeof(struct lock_struct);
-
-	if (br_lck->have_read_oplocks) {
-		data_len += 1;
-	}
-
-	DEBUG(10, ("data_len=%d\n", (int)data_len));
-
-	if (data_len == 0) {
+	if ((br_lck->num_locks == 0) && (br_lck->num_read_oplocks == 0)) {
 		/* No locks - delete this entry. */
 		NTSTATUS status = dbwrap_record_delete(br_lck->record);
 		if (!NT_STATUS_IS_OK(status)) {
@@ -1882,19 +1873,20 @@ static void byte_range_lock_flush(struct byte_range_lock *br_lck)
 			smb_panic("Could not delete byte range lock entry");
 		}
 	} else {
+		size_t lock_len, data_len;
 		TDB_DATA data;
 		NTSTATUS status;
 
+		lock_len = br_lck->num_locks * sizeof(struct lock_struct);
+		data_len = lock_len + sizeof(br_lck->num_read_oplocks);
+
 		data.dsize = data_len;
 		data.dptr = talloc_array(talloc_tos(), uint8_t, data_len);
 		SMB_ASSERT(data.dptr != NULL);
 
-		memcpy(data.dptr, br_lck->lock_data,
-		       br_lck->num_locks * sizeof(struct lock_struct));
-
-		if (br_lck->have_read_oplocks) {
-			data.dptr[data_len-1] = 1;
-		}
+		memcpy(data.dptr, br_lck->lock_data, lock_len);
+		memcpy(data.dptr + lock_len, &br_lck->num_read_oplocks,
+		       sizeof(br_lck->num_read_oplocks));
 
 		status = dbwrap_record_store(br_lck->record, data, TDB_REPLACE);
 		TALLOC_FREE(data.dptr);
@@ -1917,6 +1909,32 @@ static int byte_range_lock_destructor(struct byte_range_lock *br_lck)
 	return 0;
 }
 
+static bool brl_parse_data(struct byte_range_lock *br_lck, TDB_DATA data)
+{
+	size_t data_len;
+
+	if (data.dsize == 0) {
+		return true;
+	}
+	if (data.dsize % sizeof(struct lock_struct) !=
+	    sizeof(br_lck->num_read_oplocks)) {
+		DEBUG(1, ("Invalid data size: %u\n", (unsigned)data.dsize));
+		return false;
+	}
+
+	br_lck->num_locks = data.dsize / sizeof(struct lock_struct);
+	data_len = br_lck->num_locks * sizeof(struct lock_struct);
+
+	br_lck->lock_data = talloc_memdup(br_lck, data.dptr, data_len);
+	if (br_lck->lock_data == NULL) {
+		DEBUG(1, ("talloc_memdup failed\n"));
+		return false;
+	}
+	memcpy(&br_lck->num_read_oplocks, data.dptr + data_len,
+	       sizeof(br_lck->num_read_oplocks));
+	return true;
+}
+
 /*******************************************************************
  Fetch a set of byte range lock data from the database.
  Leave the record locked.
@@ -1926,16 +1944,14 @@ static int byte_range_lock_destructor(struct byte_range_lock *br_lck)
 struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, files_struct *fsp)
 {
 	TDB_DATA key, data;
-	struct byte_range_lock *br_lck = talloc(mem_ctx, struct byte_range_lock);
+	struct byte_range_lock *br_lck;
 
+	br_lck = talloc_zero(mem_ctx, struct byte_range_lock);
 	if (br_lck == NULL) {
 		return NULL;
 	}
 
 	br_lck->fsp = fsp;
-	br_lck->num_locks = 0;
-	br_lck->have_read_oplocks = false;
-	br_lck->modified = False;
 
 	key.dptr = (uint8 *)&fsp->file_id;
 	key.dsize = sizeof(struct file_id);
@@ -1950,30 +1966,12 @@ struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, files_struct *fsp)
 
 	data = dbwrap_record_get_value(br_lck->record);
 
-	br_lck->lock_data = NULL;
-
-	talloc_set_destructor(br_lck, byte_range_lock_destructor);
-
-	br_lck->num_locks = data.dsize / sizeof(struct lock_struct);
-
-	if (br_lck->num_locks != 0) {
-		br_lck->lock_data = talloc_array(
-			br_lck, struct lock_struct, br_lck->num_locks);
-		if (br_lck->lock_data == NULL) {
-			DEBUG(0, ("malloc failed\n"));
-			TALLOC_FREE(br_lck);
-			return NULL;
-		}
-
-		memcpy(br_lck->lock_data, data.dptr,
-		       talloc_get_size(br_lck->lock_data));
+	if (!brl_parse_data(br_lck, data)) {
+		TALLOC_FREE(br_lck);
+		return NULL;
 	}
 
-	DEBUG(10, ("data.dsize=%d\n", (int)data.dsize));
-
-	if ((data.dsize % sizeof(struct lock_struct)) == 1) {
-		br_lck->have_read_oplocks = (data.dptr[data.dsize-1] == 1);
-	}
+	talloc_set_destructor(br_lck, byte_range_lock_destructor);
 
 	if (DEBUGLEVEL >= 10) {
 		unsigned int i;
@@ -1999,28 +1997,19 @@ static void brl_get_locks_readonly_parser(TDB_DATA key, TDB_DATA data,
 {
 	struct brl_get_locks_readonly_state *state =
 		(struct brl_get_locks_readonly_state *)private_data;
-	struct byte_range_lock *br_lock;
+	struct byte_range_lock *br_lck;
 
-	br_lock = talloc_pooled_object(
+	br_lck = talloc_pooled_object(
 		state->mem_ctx, struct byte_range_lock, 1, data.dsize);
-	if (br_lock == NULL) {
+	if (br_lck == NULL) {
 		*state->br_lock = NULL;
 		return;
 	}
-	br_lock->lock_data = (struct lock_struct *)talloc_memdup(
-		br_lock, data.dptr, data.dsize);
-	br_lock->num_locks = data.dsize / sizeof(struct lock_struct);
-
-	if ((data.dsize % sizeof(struct lock_struct)) == 1) {
-		br_lock->have_read_oplocks = (data.dptr[data.dsize-1] == 1);
-	} else {
-		br_lock->have_read_oplocks = false;
+	if (!brl_parse_data(br_lck, data)) {
+		*state->br_lock = NULL;
+		return;
 	}
-
-	DEBUG(10, ("Got %d bytes, have_read_oplocks: %s\n", (int)data.dsize,
-		   br_lock->have_read_oplocks ? "true" : "false"));
-
-	*state->br_lock = br_lock;
+	*state->br_lock = br_lck;
 }
 
 struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
@@ -2052,7 +2041,7 @@ struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
 		if (br_lock == NULL) {
 			goto fail;
 		}
-		br_lock->have_read_oplocks = rw->have_read_oplocks;
+		br_lock->num_read_oplocks = rw->num_read_oplocks;
 		br_lock->num_locks = rw->num_locks;
 		br_lock->lock_data = (struct lock_struct *)talloc_memdup(
 			br_lock, rw->lock_data, lock_data_size);
@@ -2082,7 +2071,7 @@ struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
 				goto fail;
 			}
 
-			br_lock->have_read_oplocks = false;
+			br_lock->num_read_oplocks = 0;
 			br_lock->num_locks = 0;
 			br_lock->lock_data = NULL;
 
-- 
1.9.1


From 989f895603726590970bfec53dc834867780fcab Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Tue, 28 Oct 2014 15:31:46 -0700
Subject: [PATCH 04/22] s3: smbd: Implementation of SMB2.1 leases.

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 libcli/smb/smb2_lease.c             |   6 +
 libcli/smb/smb2_lease.h             |   2 +
 selftest/knownfail                  |  12 -
 source3/include/smb.h               |   1 +
 source3/librpc/idl/open_files.idl   |  30 ++
 source3/librpc/idl/wscript_build    |   1 +
 source3/librpc/wscript_build        |   7 +-
 source3/locking/locking.c           | 141 ++++++++-
 source3/locking/proto.h             |  17 +-
 source3/smbd/durable.c              |  28 +-
 source3/smbd/files.c                |  34 +++
 source3/smbd/globals.h              |  10 +-
 source3/smbd/open.c                 | 577 +++++++++++++++++++++++++++---------
 source3/smbd/oplock.c               | 364 +++++++++++++++++------
 source3/smbd/proto.h                |  14 +
 source3/smbd/server.c               |   5 +
 source3/smbd/smb2_break.c           | 194 +++++++++++-
 source3/smbd/smb2_create.c          | 151 +++++++++-
 source3/smbd/smb2_negprot.c         |   4 +
 source3/smbd/smb2_server.c          |  25 ++
 source3/utils/status.c              |   2 +
 source3/wscript_build               |   6 +
 source4/torture/smb2/durable_open.c |   5 +-
 source4/torture/smb2/lease.c        |   5 +-
 24 files changed, 1382 insertions(+), 259 deletions(-)

diff --git a/libcli/smb/smb2_lease.c b/libcli/smb/smb2_lease.c
index 6fc26f2..70dd3d4 100644
--- a/libcli/smb/smb2_lease.c
+++ b/libcli/smb/smb2_lease.c
@@ -86,3 +86,9 @@ bool smb2_lease_push(const struct smb2_lease *lease, uint8_t *buf, size_t len)
 
 	return true;
 }
+
+bool smb2_lease_key_equal(const struct smb2_lease_key *k1,
+			  const struct smb2_lease_key *k2)
+{
+	return ((k1->data[0] == k2->data[0]) && (k1->data[1] == k2->data[1]));
+}
diff --git a/libcli/smb/smb2_lease.h b/libcli/smb/smb2_lease.h
index ba8178d..9db239d 100644
--- a/libcli/smb/smb2_lease.h
+++ b/libcli/smb/smb2_lease.h
@@ -32,5 +32,7 @@
 ssize_t smb2_lease_pull(const uint8_t *buf, size_t len,
 			struct smb2_lease *lease);
 bool smb2_lease_push(const struct smb2_lease *lease, uint8_t *buf, size_t len);
+bool smb2_lease_key_equal(const struct smb2_lease_key *k1,
+			  const struct smb2_lease_key *k2);
 
 #endif /* _LIBCLI_SMB_SMB2_LEASE_H_ */
diff --git a/selftest/knownfail b/selftest/knownfail
index 3d73495..5a9a865 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -187,25 +187,13 @@
 ^samba3.smb2.notify.valid-req
 ^samba3.smb2.notify.dir
 ^samba3.smb2.notify.rec
-^samba3.smb2.durable-open.lock-lease
 ^samba3.smb2.durable-open.delete_on_close2
-^samba3.smb2.durable-v2-open.open-lease
-^samba3.smb2.durable-v2-open.persistent-open-lease
 ^samba3.smb2.durable-v2-open.app-instance
 ^samba4.smb2.ioctl.req_resume_key\(dc\) # not supported by s4 ntvfs server
 ^samba4.smb2.ioctl.copy_chunk_\w*\(dc\)	# not supported by s4 ntvfs server
 ^samba3.smb2.dir.one
 ^samba3.smb2.dir.modify
-^samba3.smb2.lease.request
-^samba3.smb2.lease.upgrade
-^samba3.smb2.lease.break
-^samba3.smb2.lease.oplock
-^samba3.smb2.lease.multibreak
 ^samba3.smb2.lease.v2_request
-^samba3.smb2.lease.v2_request_parent
-^samba3.smb2.lease.break_twice
-^samba3.smb2.lease.nobreakself
-^samba3.smb2.lease.v2_epoch1
 ^samba3.smb2.oplock.batch20
 ^samba3.smb2.oplock.stream1
 ^samba3.smb2.streams.rename
diff --git a/source3/include/smb.h b/source3/include/smb.h
index aab4ff5..7bace88 100644
--- a/source3/include/smb.h
+++ b/source3/include/smb.h
@@ -577,6 +577,7 @@ enum remote_arch_types {RA_UNKNOWN, RA_WFWG, RA_OS2, RA_WIN95, RA_WINNT,
 #define EXCLUSIVE_OPLOCK 		OPLOCK_EXCLUSIVE
 #define BATCH_OPLOCK 			OPLOCK_BATCH
 #define LEVEL_II_OPLOCK 		OPLOCK_LEVEL_II
+#define LEASE_OPLOCK			0x100
 
 /* The following are Samba-private. */
 #define INTERNAL_OPEN_ONLY 		0x8
diff --git a/source3/librpc/idl/open_files.idl b/source3/librpc/idl/open_files.idl
index 4278301..48e6bbe 100644
--- a/source3/librpc/idl/open_files.idl
+++ b/source3/librpc/idl/open_files.idl
@@ -3,6 +3,8 @@
 import "server_id.idl";
 import "security.idl";
 import "file_id.idl";
+import "smb2_lease_struct.idl";
+import "misc.idl";
 
 [
 	pointer_default(unique)
@@ -14,6 +16,7 @@ interface open_files
 		server_id	pid;
 		hyper		op_mid;
 		uint16		op_type;
+		uint32		lease_idx;
 		uint32		access_mask;
 		uint32		share_access;
 		uint32		private_options;
@@ -31,6 +34,31 @@ interface open_files
 		[skip] boolean8	stale;
 	} share_mode_entry;
 
+	typedef [public,bitmap8bit] bitmap {
+		SHARE_MODE_NO_CACHING = 0x00,
+		SHARE_MODE_READ_CACHING = 0x01,
+		SHARE_MODE_HANDLE_CACHING = 0x02,
+		SHARE_MODE_WRITE_CACHING = 0x04
+	} share_mode_caching;
+
+	typedef [public,flag(NDR_PAHEX)] struct {
+		GUID			client_guid;
+		smb2_lease_key		lease_key;
+		share_mode_caching      current_state;
+		/*
+		 * breaking_to_state indicates to which level
+		 * the current state is broken when a conflicting
+		 * request is processed. The calculation is as follows:
+		 *
+		 *   breaking_to_state = current_state;
+		 *   breaking_to_state &= ~(remove_state)
+		 *   breaking_to_state &= allowed_shared_state
+		 */
+		share_mode_caching      breaking_to_state;
+		boolean8                breaking;
+		uint16			epoch;
+	} share_mode_oplock;
+
 	typedef [public] struct {
 		uint32		name_hash;
 		security_token *delete_nt_token;
@@ -43,6 +71,8 @@ interface open_files
 		[string,charset(UTF8)] char *stream_name;
 		uint32 num_share_modes;
 		[size_is(num_share_modes)] share_mode_entry share_modes[];
+		uint32 num_leases;
+		[size_is(num_leases)] share_mode_oplock leases[];
 		uint32 num_delete_tokens;
 		[size_is(num_delete_tokens)] delete_token delete_tokens[];
 		timespec old_write_time;
diff --git a/source3/librpc/idl/wscript_build b/source3/librpc/idl/wscript_build
index c38fe7b..f9b1bd7 100644
--- a/source3/librpc/idl/wscript_build
+++ b/source3/librpc/idl/wscript_build
@@ -8,6 +8,7 @@ bld.SAMBA_PIDL_LIST('PIDL',
                     '''messaging.idl libnetapi.idl open_files.idl
                        perfcount.idl secrets.idl libnet_join.idl
                        smbXsrv.idl
+                       leases_db.idl
                     ''',
                     options='--includedir=%s --header --ndr-parser' % topinclude,
                     output_dir='../gen_ndr')
diff --git a/source3/librpc/wscript_build b/source3/librpc/wscript_build
index 77ae048..5c83cf2 100644
--- a/source3/librpc/wscript_build
+++ b/source3/librpc/wscript_build
@@ -17,7 +17,7 @@ bld.SAMBA3_SUBSYSTEM('NDR_MESSAGING',
 
 bld.SAMBA3_SUBSYSTEM('NDR_OPEN_FILES',
 	source='gen_ndr/ndr_open_files.c',
-	public_deps='ndr NDR_SERVER_ID NDR_FILE_ID NDR_SECURITY'
+	public_deps='ndr NDR_SERVER_ID NDR_FILE_ID NDR_SECURITY NDR_SMB2_LEASE_STRUCT'
 	)
 
 bld.SAMBA3_SUBSYSTEM('NDR_SMBXSRV',
@@ -25,6 +25,11 @@ bld.SAMBA3_SUBSYSTEM('NDR_SMBXSRV',
 	public_deps='ndr NDR_SERVER_ID NDR_SECURITY NDR_AUTH'
 	)
 
+bld.SAMBA3_SUBSYSTEM('NDR_LEASES_DB',
+	source='gen_ndr/ndr_leases_db.c',
+	public_deps='ndr'
+	)
+
 bld.SAMBA3_SUBSYSTEM('NDR_SECRETS',
 	source='gen_ndr/ndr_secrets.c',
 	public_deps='ndr'
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index a320068..64bd043 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -46,6 +46,7 @@
 #include "messages.h"
 #include "util_tdb.h"
 #include "../librpc/gen_ndr/ndr_open_files.h"
+#include "locking/leases_db.h"
 
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_LOCKING
@@ -608,6 +609,7 @@ bool is_valid_share_mode_entry(const struct share_mode_entry *e)
 	num_props += ((e->op_type == NO_OPLOCK) ? 1 : 0);
 	num_props += (EXCLUSIVE_OPLOCK_TYPE(e->op_type) ? 1 : 0);
 	num_props += (LEVEL_II_OPLOCK_TYPE(e->op_type) ? 1 : 0);
+	num_props += (e->op_type == LEASE_OPLOCK);
 
 	if ((num_props > 1) && serverid_exists(&e->pid)) {
 		smb_panic("Invalid share mode entry");
@@ -694,7 +696,8 @@ void remove_stale_share_mode_entries(struct share_mode_data *d)
 }
 
 bool set_share_mode(struct share_mode_lock *lck, files_struct *fsp,
-		    uid_t uid, uint64_t mid, uint16 op_type)
+		    uid_t uid, uint64_t mid, uint16 op_type,
+		    uint32_t lease_idx)
 {
 	struct share_mode_data *d = lck->data;
 	struct share_mode_entry *tmp, *e;
@@ -716,6 +719,7 @@ bool set_share_mode(struct share_mode_lock *lck, files_struct *fsp,
 	e->access_mask = fsp->access_mask;
 	e->op_mid = mid;
 	e->op_type = op_type;
+	e->lease_idx = lease_idx;
 	e->time.tv_sec = fsp->open_time.tv_sec;
 	e->time.tv_usec = fsp->open_time.tv_usec;
 	e->id = fsp->file_id;
@@ -816,16 +820,70 @@ bool mark_share_mode_disconnected(struct share_mode_lock *lck,
 
 bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp)
 {
+	struct share_mode_data *d = lck->data;
 	struct share_mode_entry *e;
+	uint16_t op_type;
+	uint32_t lease_idx;
+	uint32_t i;
 
 	e = find_share_mode_entry(lck, fsp);
 	if (e == NULL) {
 		return False;
 	}
 
+	op_type = e->op_type;
 	e->op_type = NO_OPLOCK;
-	lck->data->modified = True;
-	return True;
+
+	d->modified = True;
+
+	if (op_type != LEASE_OPLOCK) {
+		return true;
+	}
+
+	/*
+	 * This used to reference a lease. If there's no other one referencing
+	 * it, remove it.
+	 */
+
+	lease_idx = e->lease_idx;
+	e->lease_idx = UINT32_MAX;
+
+	for (i=0; i<d->num_share_modes; i++) {
+		if (d->share_modes[i].lease_idx == lease_idx) {
+			break;
+		}
+	}
+	if (i < d->num_share_modes) {
+		/*
+		 * Found another one
+		 */
+		return true;
+	}
+
+	d->num_leases -= 1;
+	d->leases[lease_idx] = d->leases[d->num_leases];
+
+	/*
+	 * We changed the lease array. Fix all references to it.
+	 */
+	for (i=0; i<d->num_share_modes; i++) {
+		if (d->share_modes[i].lease_idx == d->num_leases) {
+			d->share_modes[i].lease_idx = lease_idx;
+		}
+	}
+
+	{
+		NTSTATUS status;
+
+		status = leases_db_del(
+			&fsp->conn->sconn->client->connections->smb2.client.guid,
+			&fsp->lease->lease.lease_key);
+
+		DEBUG(10, ("%s: leases_db_del returned %s\n", __func__,
+			   nt_errstr(status)));
+	}
+
+	return true;
 }
 
 /*******************************************************************
@@ -846,6 +904,83 @@ bool downgrade_share_oplock(struct share_mode_lock *lck, files_struct *fsp)
 	return True;
 }
 
+/*
+ * "key" has been broken, it is referenced somewhere in "lck". If "fsp"
+ * applies to it, reset fsp->sent_oplock_break.
+ */
+
+bool fsp_lease_broken(struct share_mode_lock *lck,
+		      struct file_id lck_id,
+		      const struct smb2_lease_key *key,
+		      files_struct *fsp, uint32_t new_lease_state)
+{
+	struct share_mode_data *d = lck->data;
+	struct share_mode_entry *e;
+
+	if (!file_id_equal(&fsp->file_id, &lck_id)) {
+		return false;
+	}
+	if (fsp->oplock_type != LEASE_OPLOCK) {
+		return false;
+	}
+
+	e = find_share_mode_entry(lck, fsp);
+	if (e == NULL) {
+		DEBUG(1, ("downgrade_lease_fsps: Could not find share mode "
+			  "entry\n"));
+		return false;
+	}
+
+	if (!smb2_lease_key_equal(key, &d->leases[e->lease_idx].lease_key)) {
+		return false;
+	}
+	fsp->sent_oplock_break = NO_BREAK_SENT;
+	TALLOC_FREE(fsp->oplock_timeout);
+	fsp->lease->lease.lease_state = new_lease_state;
+	return true;
+}
+
+NTSTATUS downgrade_share_lease(struct smbd_server_connection *sconn,
+			       struct share_mode_lock *lck,
+			       const struct smb2_lease_key *key,
+			       uint32_t new_lease_state)
+{
+	struct share_mode_data *d = lck->data;
+	struct share_mode_oplock *l;
+	uint32_t i;
+
+	for (i=0; i<d->num_leases; i++) {
+		if (smb2_lease_key_equal(key, &d->leases[i].lease_key)) {
+			break;
+		}
+	}
+	if (i == d->num_leases) {
+		DEBUG(10, ("lease not found\n"));
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	l = &d->leases[i];
+
+	/*
+	 * Can't upgrade anything: l->current_state must be a strict bitwise
+	 * superset of new_lease_state
+	 */
+
+	if ((new_lease_state & l->current_state) != new_lease_state) {
+		DEBUG(10, ("Attempt to upgrade from %d to %d\n",
+			   (int)l->current_state, (int)new_lease_state));
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	l->current_state = new_lease_state;
+	l->breaking_to_state = 0;
+	l->breaking = 0;
+
+	d->modified = true;
+
+	return NT_STATUS_OK;
+}
+
 /****************************************************************************
  Adds a delete on close token.
 ****************************************************************************/
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index 46eec2a..a347866 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -30,9 +30,9 @@ void brl_shutdown(void);
 
 unsigned int brl_num_locks(const struct byte_range_lock *brl);
 struct files_struct *brl_fsp(struct byte_range_lock *brl);
-bool brl_have_read_oplocks(const struct byte_range_lock *brl);
-void brl_set_have_read_oplocks(struct byte_range_lock *brl,
-			       bool have_read_oplocks);
+uint32_t brl_num_read_oplocks(const struct byte_range_lock *brl);
+void brl_set_num_read_oplocks(struct byte_range_lock *brl,
+			      uint32_t num_read_oplocks);
 
 NTSTATUS brl_lock_windows_default(struct byte_range_lock *br_lck,
 		struct lock_struct *plock,
@@ -167,13 +167,22 @@ void get_file_infos(struct file_id id,
 bool is_valid_share_mode_entry(const struct share_mode_entry *e);
 bool share_mode_stale_pid(struct share_mode_data *d, uint32_t idx);
 bool set_share_mode(struct share_mode_lock *lck, files_struct *fsp,
-		    uid_t uid, uint64_t mid, uint16 op_type);
+		    uid_t uid, uint64_t mid, uint16 op_type,
+		    uint32_t lease_idx);
 void remove_stale_share_mode_entries(struct share_mode_data *d);
 bool del_share_mode(struct share_mode_lock *lck, files_struct *fsp);
 bool mark_share_mode_disconnected(struct share_mode_lock *lck,
 				  struct files_struct *fsp);
 bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp);
 bool downgrade_share_oplock(struct share_mode_lock *lck, files_struct *fsp);
+bool fsp_lease_broken(struct share_mode_lock *lck,
+		      struct file_id lck_id,
+		      const struct smb2_lease_key *key,
+		      files_struct *fsp, uint32_t new_lease_state);
+NTSTATUS downgrade_share_lease(struct smbd_server_connection *sconn,
+			       struct share_mode_lock *lck,
+			       const struct smb2_lease_key *key,
+			       uint32_t new_lease_state);
 bool get_delete_on_close_token(struct share_mode_lock *lck,
 				uint32_t name_hash,
 				const struct security_token **pp_nt_tok,
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index 9489cf1..660865d 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -168,7 +168,7 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
 		return NT_STATUS_INVALID_PARAMETER;
 	}
 
-	if (!BATCH_OPLOCK_TYPE(fsp->oplock_type)) {
+	if ((fsp_lease_type(fsp) & SMB2_LEASE_HANDLE) == 0) {
 		return NT_STATUS_NOT_SUPPORTED;
 	}
 
@@ -724,6 +724,32 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
 	fsp->aio_write_behind = false;
 	fsp->oplock_type = e->op_type;
 
+	if (fsp->oplock_type == LEASE_OPLOCK) {
+		struct share_mode_oplock *o = &lck->data->leases[e->lease_idx];
+		struct smb2_lease_key key;
+
+		key.data[0] = o->lease_key.data[0];
+		key.data[1] = o->lease_key.data[1];
+
+		fsp->lease = find_fsp_lease(fsp, &key);
+
+		if (fsp->lease != NULL) {
+			fsp->lease->ref_count += 1;
+		} else {
+			fsp->lease = talloc_zero(fsp->conn->sconn,
+						 struct fsp_lease);
+			if (fsp->lease == NULL) {
+				TALLOC_FREE(lck);
+				fsp_free(fsp);
+				return NT_STATUS_NO_MEMORY;
+			}
+			fsp->lease->ref_count = 1;
+			fsp->lease->lease.lease_key = key;
+			fsp->lease->lease.lease_state = o->current_state;
+			fsp->lease->lease.lease_epoch = o->epoch;
+		}
+	}
+
 	fsp->initial_allocation_size = cookie.initial_allocation_size;
 	fsp->fh->position_information = cookie.position_information;
 	fsp->update_write_time_triggered = cookie.update_write_time_triggered;
diff --git a/source3/smbd/files.c b/source3/smbd/files.c
index a9e8357..13d1138 100644
--- a/source3/smbd/files.c
+++ b/source3/smbd/files.c
@@ -387,6 +387,24 @@ files_struct *file_find_di_next(files_struct *start_fsp)
 	return NULL;
 }
 
+struct files_struct *file_find_one_fsp_from_lease_key(
+	struct smbd_server_connection *sconn,
+	const struct smb2_lease_key *lease_key)
+{
+	struct files_struct *fsp;
+
+	for (fsp = sconn->files; fsp; fsp=fsp->next) {
+		if ((fsp->lease != NULL) &&
+		    (fsp->lease->lease.lease_key.data[0] ==
+		     lease_key->data[0]) &&
+		    (fsp->lease->lease.lease_key.data[1] ==
+		     lease_key->data[1])) {
+			return fsp;
+		}
+	}
+	return NULL;
+}
+
 /****************************************************************************
  Find any fsp open with a pathname below that of an already open path.
 ****************************************************************************/
@@ -472,6 +490,14 @@ void fsp_free(files_struct *fsp)
 		fsp->fh->ref_count--;
 	}
 
+	if (fsp->lease != NULL) {
+		if (fsp->lease->ref_count == 1) {
+			TALLOC_FREE(fsp->lease);
+		} else {
+			fsp->lease->ref_count--;
+		}
+	}
+
 	fsp->conn->num_files_open--;
 
 	/* this is paranoia, just in case someone tries to reuse the
@@ -740,3 +766,11 @@ NTSTATUS fsp_set_smb_fname(struct files_struct *fsp,
 			smb_fname_str_dbg(fsp->fsp_name),
 			&fsp->name_hash);
 }
+
+uint32_t fsp_lease_type(struct files_struct *fsp)
+{
+	if (fsp->oplock_type == LEASE_OPLOCK) {
+		return fsp->lease->lease.lease_state;
+	}
+	return map_oplock_to_lease_type(fsp->oplock_type);
+}
diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h
index 36e7f0f..39c537c 100644
--- a/source3/smbd/globals.h
+++ b/source3/smbd/globals.h
@@ -250,6 +250,14 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn,
 				     struct smbXsrv_tcon *tcon,
 				     struct smbXsrv_open *op,
 				     uint8_t oplock_level);
+NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn,
+				    struct smbXsrv_session *session,
+				    struct smbXsrv_tcon *tcon,
+				    uint16_t new_epoch,
+				    uint32_t lease_flags,
+				    struct smb2_lease_key *lease_key,
+				    uint32_t current_lease_state,
+				    uint32_t new_lease_state);
 
 NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
 					 struct tevent_req *subreq,
@@ -298,7 +306,7 @@ void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
 struct deferred_open_record;
 
 /* SMB1 -> SMB2 glue. */
-void send_break_message_smb2(files_struct *fsp, int level);
+void send_break_message_smb2(files_struct *fsp, uint32_t break_to);
 struct blocking_lock_record *get_pending_smb2req_blr(struct smbd_smb2_request *smb2req);
 bool push_blocking_lock_request_smb2( struct byte_range_lock *br_lck,
 				struct smb_request *req,
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index ccea1e9..8d49030 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -35,6 +35,12 @@
 #include "serverid.h"
 #include "messages.h"
 #include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "locking/leases_db.h"
+
+static bool is_same_lease(const struct share_mode_data *d,
+			  const struct share_mode_entry *e,
+			  const struct smb2_lease *lease);
+
 
 extern const struct generic_mapping file_generic_mapping;
 
@@ -1257,6 +1263,10 @@ static NTSTATUS send_break_message(struct messaging_context *msg_ctx,
 	share_mode_entry_to_message(msg, exclusive);
 
 	/* Overload entry->op_type */
+	/*
+	 * This is a cut from uint32 to uint16, but so far only the lower 3
+	 * bits (LEASE_WRITE/HANDLE/READ are used anyway.
+	 */
 	SSVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET, break_to);
 
 	status = messaging_send_buf(msg_ctx, exclusive->pid,
@@ -1376,31 +1386,28 @@ static bool validate_oplock_types(struct share_mode_lock *lck)
 
 static bool delay_for_oplock(files_struct *fsp,
 			     int oplock_request,
+			     const struct smb2_lease *lease,
 			     struct share_mode_lock *lck,
 			     bool have_sharing_violation,
 			     uint32_t create_disposition)
 {
 	struct share_mode_data *d = lck->data;
-	struct share_mode_entry *entry;
 	uint32_t num_non_stat_opens = 0;
 	uint32_t i;
-	uint16_t break_to;
+	bool have_broken = false;
+	bool will_overwrite;
 
-	if ((oplock_request & INTERNAL_OPEN_ONLY) || is_stat_open(fsp->access_mask)) {
+	if ((oplock_request & INTERNAL_OPEN_ONLY) ||
+	    is_stat_open(fsp->access_mask)) {
 		return false;
 	}
+
 	for (i=0; i<d->num_share_modes; i++) {
 		struct share_mode_entry *e = &d->share_modes[i];
 		if (e->op_type == NO_OPLOCK && is_stat_open(e->access_mask)) {
 			continue;
 		}
 		num_non_stat_opens += 1;
-
-		/*
-		 * We found the a non-stat open, which in the exclusive/batch
-		 * case will be inspected further down.
-		 */
-		entry = e;
 	}
 	if (num_non_stat_opens == 0) {
 		/*
@@ -1408,74 +1415,98 @@ static bool delay_for_oplock(files_struct *fsp,
 		 */
 		return false;
 	}
-	if (num_non_stat_opens != 1) {
-		/*
-		 * More than one open around. There can't be any exclusive or
-		 * batch left, this is all level2.
-		 */
-		return false;
+
+	if (have_sharing_violation) {
+		for (i=0; i<d->num_share_modes; i++) {
+			struct share_mode_entry *e = &d->share_modes[i];
+			uint32_t e_lease_type = get_lease_type(d, e);
+
+			if (!(e_lease_type & SMB2_LEASE_HANDLE)) {
+				continue;
+			}
+			if (is_same_lease(d, e, lease)) {
+				continue;
+			}
+			if (share_mode_stale_pid(d, i)) {
+				continue;
+			}
+			send_break_message(fsp->conn->sconn->msg_ctx, e,
+					   e_lease_type & ~SMB2_LEASE_HANDLE);
+			have_broken = true;
+		}
 	}
 
-	if (server_id_is_disconnected(&entry->pid)) {
-		/*
-		 * TODO: clean up.
-		 * This could be achieved by sending a break message
-		 * to ourselves. Special considerations for files
-		 * with delete_on_close flag set!
-		 *
-		 * For now we keep it simple and do not
-		 * allow delete on close for durable handles.
-		 */
+	if (have_broken) {
+		return true;
+	}
+	if (have_sharing_violation) {
 		return false;
 	}
 
 	switch (create_disposition) {
 	case FILE_SUPERSEDE:
 	case FILE_OVERWRITE_IF:
-		break_to = NO_OPLOCK;
+		will_overwrite = true;
 		break;
 	default:
-		break_to = LEVEL_II_OPLOCK;
+		will_overwrite = false;
 		break;
 	}
 
-	if (have_sharing_violation && (entry->op_type & BATCH_OPLOCK)) {
-		if (share_mode_stale_pid(d, 0)) {
-			return false;
+	for (i=0; i<d->num_share_modes; i++) {
+		struct share_mode_entry *e = &d->share_modes[i];
+		uint32_t e_lease_type = get_lease_type(d, e);
+
+		DEBUG(10, ("entry %u: e_lease_type %u, will_overwrite: %u\n",
+			   (unsigned)i, (unsigned)e_lease_type,
+			   (unsigned)will_overwrite));
+
+		if (e_lease_type & SMB2_LEASE_WRITE) {
+			uint32_t break_to;
+
+			if (share_mode_stale_pid(d, i)) {
+				return false;
+			}
+			if ((e->op_type == LEASE_OPLOCK) &&
+			    (lease != NULL) &&
+			    smb2_lease_key_equal(
+				    &lease->lease_key,
+				    &d->leases[e->lease_idx].lease_key)) {
+				return false;
+			}
+
+			break_to = e_lease_type & ~SMB2_LEASE_WRITE;
+			if (will_overwrite) {
+				/*
+				 * There's no H only lease that we could break
+				 * to
+				 */
+				break_to = SMB2_LEASE_NONE;
+			}
+
+			DEBUG(10, ("breaking SMB2_LEASE_WRITE to %d\n",
+				   (int)break_to));
+			send_break_message(fsp->conn->sconn->msg_ctx, e,
+					   break_to);
+			return true;
 		}
-		send_break_message(fsp->conn->sconn->msg_ctx, entry, break_to);
-		return true;
-	}
-	if (have_sharing_violation) {
-		/*
-		 * Non-batch exclusive is not broken if we have a sharing
-		 * violation
-		 */
-		return false;
-	}
-	if (LEVEL_II_OPLOCK_TYPE(entry->op_type) &&
-	    (break_to == NO_OPLOCK)) {
-		if (share_mode_stale_pid(d, 0)) {
-			return false;
+
+		if (will_overwrite && (e_lease_type & SMB2_LEASE_READ)) {
+			if (share_mode_stale_pid(d, i)) {
+				continue;
+			}
+			DEBUG(10, ("breaking SMB2_LEASE_READ\n"));
+			send_break_message(fsp->conn->sconn->msg_ctx, e,
+					   SMB2_LEASE_NONE);
+			/*
+			 * This is an async break. No need to wait for a
+			 * response.
+			 */
+			continue;
 		}
-		DEBUG(10, ("Asynchronously breaking level2 oplock for "
-			   "create_disposition=%u\n",
-			   (unsigned)create_disposition));
-		send_break_message(fsp->conn->sconn->msg_ctx, entry, break_to);
-		return false;
-	}
-	if (!EXCLUSIVE_OPLOCK_TYPE(entry->op_type)) {
-		/*
-		 * No break for NO_OPLOCK or LEVEL2_OPLOCK oplocks
-		 */
-		return false;
-	}
-	if (share_mode_stale_pid(d, 0)) {
-		return false;
 	}
 
-	send_break_message(fsp->conn->sconn->msg_ctx, entry, break_to);
-	return true;
+	return have_broken;
 }
 
 static bool file_has_brlocks(files_struct *fsp)
@@ -1489,88 +1520,330 @@ static bool file_has_brlocks(files_struct *fsp)
 	return (brl_num_locks(br_lck) > 0);
 }
 
-static void grant_fsp_oplock_type(files_struct *fsp,
-				  struct share_mode_lock *lck,
-				  int oplock_request)
+static int find_share_mode_oplock(struct share_mode_data *d,
+				  const struct GUID *client_guid,
+				  const struct smb2_lease_key *key)
 {
-	bool allow_level2 = (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
-		            lp_level2_oplocks(SNUM(fsp->conn));
-	bool got_level2_oplock, got_a_none_oplock;
-	uint32_t i;
+	uint16_t i;
 
-	/* Start by granting what the client asked for,
-	   but ensure no SAMBA_PRIVATE bits can be set. */
-	fsp->oplock_type = (oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
+	for (i=0; i<d->num_leases; i++) {
+		struct share_mode_oplock *l = &d->leases[i];
+		if (GUID_equal(client_guid, &l->client_guid) &&
+		    smb2_lease_key_equal(key, &l->lease_key)) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+static bool is_same_lease(const struct share_mode_data *d,
+			  const struct share_mode_entry *e,
+			  const struct smb2_lease *lease)
+{
+	if (e->op_type != LEASE_OPLOCK) {
+		return false;
+	}
+	if (lease == NULL) {
+		return false;
+	}
+	return smb2_lease_key_equal(&d->leases[e->lease_idx].lease_key,
+				    &lease->lease_key);
+}
+
+struct fsp_lease *find_fsp_lease(files_struct *new_fsp,
+				 const struct smb2_lease_key *key)
+{
+	files_struct *fsp;
+
+	/*
+	 * TODO: Measure how expensive this loop is with thousands of open
+	 * handles...
+	 */
+
+	for (fsp = file_find_di_first(new_fsp->conn->sconn, new_fsp->file_id);
+	     fsp != NULL;
+	     fsp = file_find_di_next(fsp)) {
+
+		if (fsp == new_fsp) {
+			continue;
+		}
+		if (fsp->oplock_type != LEASE_OPLOCK) {
+			continue;
+		}
+		if (smb2_lease_key_equal(&fsp->lease->lease.lease_key, key)) {
+			return fsp->lease;
+		}
+	}
+
+	return NULL;
+}
+
+static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d,
+				const struct smb2_lease *lease,
+				uint32_t *p_lease_idx,
+				uint32_t granted)
+{
+	const struct GUID *client_guid;
+	struct share_mode_oplock *o;
+	struct share_mode_oplock *tmp;
+	NTSTATUS status;
+	int idx;
+
+
+	/*
+	 * TODO: in future we can have multiple connections...
+	 */
+	client_guid = &fsp->conn->sconn->client->connections->smb2.client.guid;
+
+	idx = find_share_mode_oplock(d, client_guid, &lease->lease_key);
+
+	if (idx != -1) {
+
+		bool do_upgrade;
+		uint32_t existing, requested;
+
+		fsp->lease = find_fsp_lease(fsp, &lease->lease_key);
+		if (fsp->lease == NULL) {
+			DEBUG(1, ("Did not find existing lease for file %s\n",
+				  fsp_str_dbg(fsp)));
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+		fsp->lease->ref_count += 1;
+
+		*p_lease_idx = idx;
+		o = &d->leases[idx];
+
+		/*
+		 * Upgrade only if the requested lease is a strict upgrade.
+		 */
+		existing = o->current_state;
+		requested = lease->lease_state;
+
+		/*
+		 * Tricky: This test makes sure that "requested" is a
+		 * strict bitwise superset of "existing".
+		 */
+		do_upgrade = ((existing & requested) == existing);
+
+		/*
+		 * Upgrade only if other leases don't prevent what was asked
+		 * for.
+		 */
+		do_upgrade &= (granted == requested);
+
+		DEBUG(10, ("existing=%"PRIu32", requested=%"PRIu32", "
+			   "granted=%"PRIu32", do_upgrade=%d\n",
+			   existing, requested, granted, (int)do_upgrade));
+
+		if (do_upgrade) {
+			o->current_state = granted;
+		}
+		fsp->lease->lease.lease_state = o->current_state;
+		return NT_STATUS_OK;
+	}
+
+	/*
+	 * Create new lease
+	 */
+
+	tmp = talloc_realloc(d, d->leases, struct share_mode_oplock,
+			     d->num_leases+1);
+	if (tmp == NULL) {
+		/*
+		 * See [MS-SMB2]
+		 */
+		return NT_STATUS_INSUFFICIENT_RESOURCES;
+	}
+	d->leases = tmp;
+
+	fsp->lease = talloc(fsp->conn->sconn, struct fsp_lease);
+	if (fsp->lease == NULL) {
+		return NT_STATUS_INSUFFICIENT_RESOURCES;
+	}
+	fsp->lease->ref_count = 1;
+	fsp->lease->lease = *lease;
+	fsp->lease->lease.lease_state = granted;
+	fsp->lease->lease.lease_epoch += 1;
+
+	*p_lease_idx = d->num_leases;
+
+	d->leases[d->num_leases] = (struct share_mode_oplock) {
+		.client_guid = *client_guid,
+		.lease_key = lease->lease_key,
+		.epoch = lease->lease_epoch,
+		.current_state = granted,
+	};
+
+	status = leases_db_add(client_guid, &lease->lease_key,
+			       &fsp->file_id, fsp->fsp_name->base_name,
+			       fsp->fsp_name->stream_name);
+	if (!NT_STATUS_IS_OK(status)) {
+		DEBUG(10, ("%s: leases_db_add failed: %s\n", __func__,
+			   nt_errstr(status)));
+		TALLOC_FREE(fsp->lease);
+		return NT_STATUS_INSUFFICIENT_RESOURCES;
+	}
+
+	d->num_leases += 1;
+	d->modified = true;
+
+	return NT_STATUS_OK;
+
+}
+
+static NTSTATUS grant_fsp_oplock_type(struct smb_request *req, files_struct *fsp,
+				      struct share_mode_lock *lck,
+				      int oplock_request,
+				      uint32_t create_disposition,
+				      struct smb2_lease *lease)
+{
+	struct share_mode_data *d = lck->data;
+	bool got_handle_lease, got_oplock;
+	uint32_t i;
+	uint32_t granted;
+	uint32_t lease_idx = UINT32_MAX;
+	NTSTATUS status;
+	bool ret;
 
 	if (oplock_request & INTERNAL_OPEN_ONLY) {
 		/* No oplocks on internal open. */
-		fsp->oplock_type = NO_OPLOCK;
+		oplock_request = NO_OPLOCK;
 		DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n",
 			fsp->oplock_type, fsp_str_dbg(fsp)));
-		return;
+	}
+
+	if (oplock_request == LEASE_OPLOCK) {
+		granted = lease->lease_state;
+
+		if ((granted & (SMB2_LEASE_READ|SMB2_LEASE_WRITE)) == 0) {
+			DEBUG(10, ("No read or write lease requested\n"));
+			granted = SMB2_LEASE_NONE;
+		}
+		if (granted == SMB2_LEASE_WRITE) {
+			DEBUG(10, ("pure write lease requested\n"));
+			granted = SMB2_LEASE_NONE;
+		}
+		if (granted == (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+			DEBUG(10, ("write and handle lease requested\n"));
+			granted = SMB2_LEASE_NONE;
+		}
+	} else {
+		granted = map_oplock_to_lease_type(
+			oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
 	}
 
 	if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) {
 		DEBUG(10,("grant_fsp_oplock_type: file %s has byte range locks\n",
 			fsp_str_dbg(fsp)));
-		fsp->oplock_type = NO_OPLOCK;
+		granted &= ~SMB2_LEASE_READ;
 	}
 
-	if (is_stat_open(fsp->access_mask)) {
-		/* Leave the value already set. */
-		DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n",
-			fsp->oplock_type, fsp_str_dbg(fsp)));
-		return;
-	}
+	got_handle_lease = false;
+	got_oplock = false;
 
-	got_level2_oplock = false;
-	got_a_none_oplock = false;
+	for (i=0; i<d->num_share_modes; i++) {
+		struct share_mode_entry *e = &d->share_modes[i];
+		uint32_t e_lease_type;
 
-	for (i=0; i<lck->data->num_share_modes; i++) {
-		int op_type = lck->data->share_modes[i].op_type;
+		e_lease_type = get_lease_type(d, e);
 
-		if (LEVEL_II_OPLOCK_TYPE(op_type)) {
-			got_level2_oplock = true;
-		}
-		if (op_type == NO_OPLOCK) {
-			got_a_none_oplock = true;
+		if ((granted & SMB2_LEASE_WRITE) &&
+		    !is_same_lease(d, e, lease) &&
+		    !share_mode_stale_pid(d, i)) {
+			/*
+			 * Can grant only one writer
+			 */
+			granted &= ~SMB2_LEASE_WRITE;
 		}
-	}
-
-	/*
-	 * Match what was requested (fsp->oplock_type) with
- 	 * what was found in the existing share modes.
- 	 */
 
-	if (got_level2_oplock || got_a_none_oplock) {
-		if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
-			fsp->oplock_type = LEVEL_II_OPLOCK;
+		if ((e_lease_type & SMB2_LEASE_HANDLE) && !got_handle_lease &&
+		    !share_mode_stale_pid(d, i)) {
+			got_handle_lease = true;
 		}
-	}
 
-	/*
-	 * Don't grant level2 to clients that don't want them
-	 * or if we've turned them off.
-	 */
-	if (fsp->oplock_type == LEVEL_II_OPLOCK && !allow_level2) {
-		fsp->oplock_type = NO_OPLOCK;
+		if ((e->op_type != LEASE_OPLOCK) && !got_oplock &&
+		    !share_mode_stale_pid(d, i)) {
+			got_oplock = true;
+		}
 	}
 
-	if (fsp->oplock_type == LEVEL_II_OPLOCK && !got_level2_oplock) {
+	if (is_stat_open(fsp->access_mask) &&
+	    ((create_disposition == FILE_OPEN) ||
+	     (create_disposition == FILE_OPEN_IF))) {
 		/*
-		 * We're the first level2 oplock. Indicate that in brlock.tdb.
+		 * No-overwrite stat open doesn't get oplocks
 		 */
-		struct byte_range_lock *brl;
+		granted = SMB2_LEASE_NONE;
+	}
+
+	if (oplock_request == LEASE_OPLOCK) {
+
+		fsp->oplock_type = LEASE_OPLOCK;
+
+		if (got_oplock) {
+			granted &= SMB2_LEASE_READ;
+		}
+
+		status = grant_fsp_lease(fsp, lck->data, lease, &lease_idx,
+					 granted);
+		if (!NT_STATUS_IS_OK(status)) {
+			return status;
+
+		}
+		lease->lease_state = d->leases[lease_idx].current_state;
+		DEBUG(10, ("lease_state=%d\n", lease->lease_state));
+	} else {
+		switch (granted) {
+		case SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE:
+			fsp->oplock_type = BATCH_OPLOCK|EXCLUSIVE_OPLOCK;
+			break;
+		case SMB2_LEASE_READ|SMB2_LEASE_WRITE:
+			fsp->oplock_type = EXCLUSIVE_OPLOCK;
+			break;
+		case SMB2_LEASE_READ|SMB2_LEASE_HANDLE:
+		case SMB2_LEASE_READ:
+			fsp->oplock_type = LEVEL_II_OPLOCK;
+			break;
+		default:
+			fsp->oplock_type = NO_OPLOCK;
+			break;
+		}
+		if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+			bool allow_level2 =
+				(global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
+				lp_level2_oplocks(SNUM(fsp->conn));
 
-		brl = brl_get_locks(talloc_tos(), fsp);
-		if (brl != NULL) {
-			brl_set_have_read_oplocks(brl, true);
-			TALLOC_FREE(brl);
+			if (!allow_level2) {
+				fsp->oplock_type = NO_OPLOCK;
+			}
+		}
+		if (got_handle_lease) {
+			fsp->oplock_type = NO_OPLOCK;
 		}
 	}
 
 	DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n",
 		  fsp->oplock_type, fsp_str_dbg(fsp)));
+
+	status = set_file_oplock(fsp);
+	if (!NT_STATUS_IS_OK(status)) {
+		/*
+		 * Could not get the kernel oplock
+		 */
+		fsp->oplock_type = NO_OPLOCK;
+	}
+
+	if (!set_share_mode(lck, fsp, get_current_uid(fsp->conn),
+			    req ? req->mid : 0,
+			    fsp->oplock_type, lease_idx)) {
+		return NT_STATUS_NO_MEMORY;
+	}
+	ret = update_num_read_oplocks(fsp, lck);
+	if (!ret) {
+		del_share_mode(lck, fsp);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+	return NT_STATUS_OK;
 }
 
 static bool request_timed_out(struct timeval request_time,
@@ -2465,8 +2738,10 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 			smb_panic("validate_oplock_types failed");
 		}
 
-		if (delay_for_oplock(fsp, 0, lck, false, create_disposition)) {
-			schedule_defer_open(lck, fsp->file_id, request_time, req);
+		if (delay_for_oplock(fsp, 0, lease, lck, false,
+				     create_disposition)) {
+			schedule_defer_open(lck, fsp->file_id, request_time,
+					    req);
 			TALLOC_FREE(lck);
 			DEBUG(10, ("Sent oplock break request to kernel "
 				   "oplock holder\n"));
@@ -2587,7 +2862,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
 	if ((req != NULL) &&
 	    delay_for_oplock(
-		    fsp, oplock_request, lck,
+		    fsp, oplock_request, lease, lck,
 		    NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION),
 		    create_disposition)) {
 		schedule_defer_open(lck, fsp->file_id, request_time, req);
@@ -2738,8 +3013,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 		}
 	}
 
-	grant_fsp_oplock_type(fsp, lck, oplock_request);
-
 	/*
 	 * We have the share entry *locked*.....
 	 */
@@ -2798,13 +3071,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 		fsp->access_mask = access_mask | FILE_READ_ATTRIBUTES;
 	}
 
-	if (file_existed) {
-		/* stat opens on existing files don't get oplocks. */
-		if (is_stat_open(open_access_mask)) {
-			fsp->oplock_type = NO_OPLOCK;
-		}
-	}
-
 	if (new_file_created) {
 		info = FILE_WAS_CREATED;
 	} else {
@@ -2824,20 +3090,13 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 	 * file structs.
 	 */
 
-	status = set_file_oplock(fsp);
+	status = grant_fsp_oplock_type(req, fsp, lck, oplock_request,
+				       create_disposition, lease);
 	if (!NT_STATUS_IS_OK(status)) {
-		/*
-		 * Could not get the kernel oplock
-		 */
-		fsp->oplock_type = NO_OPLOCK;
-	}
-
-	if (!set_share_mode(lck, fsp, get_current_uid(conn),
-			    req ? req->mid : 0,
-			    fsp->oplock_type)) {
+		del_share_mode(lck, fsp);
 		TALLOC_FREE(lck);
 		fd_close(fsp);
-		return NT_STATUS_NO_MEMORY;
+		return status;
 	}
 
 	/* Handle strange delete on close create semantics. */
@@ -3331,7 +3590,7 @@ static NTSTATUS open_directory(connection_struct *conn,
 	}
 
 	if (!set_share_mode(lck, fsp, get_current_uid(conn),
-			    req ? req->mid : 0, NO_OPLOCK)) {
+			    req ? req->mid : 0, NO_OPLOCK, UINT32_MAX)) {
 		TALLOC_FREE(lck);
 		fd_close(fsp);
 		file_free(req, fsp);
@@ -3816,6 +4075,48 @@ static NTSTATUS inherit_new_acl(files_struct *fsp)
 }
 
 /*
+ * If we already have a lease, it must match the new file id. [MS-SMB2]
+ * 3.3.5.9.8 speaks about INVALID_PARAMETER if an already used lease key is
+ * used for a different file name.
+ */
+
+struct lease_fname_match_state {
+	const struct smb_filename *fname;
+	bool match;
+};
+
+static void lease_fname_match_parser(
+	struct file_id id, const char *filename, const char *stream_name,
+	void *private_data)
+{
+	struct lease_fname_match_state *state =
+		(struct lease_fname_match_state *)private_data;
+
+	state->match =
+		strequal(filename, state->fname->base_name) &&
+		strequal(stream_name, state->fname->stream_name);
+}
+
+static bool lease_fname_match(struct smbd_server_connection *sconn,
+			      struct smb2_lease_key *lease_key,
+			      const struct smb_filename *fname)
+{
+	struct lease_fname_match_state state =
+		{ .fname = fname, .match = true };
+	NTSTATUS status;
+
+	status = leases_db_parse(&sconn->client->connections->smb2.client.guid,
+				 lease_key, lease_fname_match_parser, &state);
+	if (!NT_STATUS_IS_OK(status)) {
+		/*
+		 * Not found or error means okay: We can make the lease pass
+		 */
+		return true;
+	}
+	return state.match;
+}
+
+/*
  * Wrapper around open_file_ntcreate and open_directory
  */
 
@@ -3871,6 +4172,12 @@ static NTSTATUS create_file_unixpath(connection_struct *conn,
 		oplock_request |= INTERNAL_OPEN_ONLY;
 	}
 
+	if ((lease != NULL) &&
+	    !lease_fname_match(req->sconn, &lease->lease_key, smb_fname)) {
+		status = NT_STATUS_INVALID_PARAMETER;
+		goto fail;
+	}
+
 	if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
 	    && (access_mask & DELETE_ACCESS)
 	    && !is_ntfs_stream_smb_fname(smb_fname)) {
diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c
index 17cb22e..c55fb3b 100644
--- a/source3/smbd/oplock.c
+++ b/source3/smbd/oplock.c
@@ -116,8 +116,6 @@ static void release_file_oplock(files_struct *fsp)
 
 	flush_write_cache(fsp, SAMBA_OPLOCK_RELEASE_FLUSH);
 	delete_write_cache(fsp);
-
-	TALLOC_FREE(fsp->oplock_timeout);
 }
 
 /****************************************************************************
@@ -141,8 +139,69 @@ static void downgrade_file_oplock(files_struct *fsp)
 	sconn->oplocks.exclusive_open--;
 	sconn->oplocks.level_II_open++;
 	fsp->sent_oplock_break = NO_BREAK_SENT;
+}
 
-	TALLOC_FREE(fsp->oplock_timeout);
+uint32_t map_oplock_to_lease_type(uint16_t op_type)
+{
+	uint32_t ret;
+
+	switch(op_type) {
+	case BATCH_OPLOCK:
+	case BATCH_OPLOCK|EXCLUSIVE_OPLOCK:
+		ret = SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE;
+		break;
+	case EXCLUSIVE_OPLOCK:
+		ret = SMB2_LEASE_READ|SMB2_LEASE_WRITE;
+		break;
+	case LEVEL_II_OPLOCK:
+		ret = SMB2_LEASE_READ;
+		break;
+	default:
+		ret = SMB2_LEASE_NONE;
+		break;
+	}
+	return ret;
+}
+
+uint32_t get_lease_type(struct share_mode_data *d, struct share_mode_entry *e)
+{
+	if (e->op_type == LEASE_OPLOCK) {
+		return d->leases[e->lease_idx].current_state;
+	}
+	return map_oplock_to_lease_type(e->op_type);
+}
+
+bool update_num_read_oplocks(files_struct *fsp, struct share_mode_lock *lck)
+{
+	struct share_mode_data *d = lck->data;
+	struct byte_range_lock *br_lck;
+	uint32_t num_read_oplocks = 0;
+	uint32_t i;
+
+	for (i=0; i<d->num_share_modes; i++) {
+		struct share_mode_entry *e = &d->share_modes[i];
+		uint32_t e_lease_type = get_lease_type(d, e);
+
+		if (e_lease_type & SMB2_LEASE_READ) {
+			num_read_oplocks += 1;
+		}
+	}
+
+	br_lck = brl_get_locks_readonly(fsp);
+	if (br_lck == NULL) {
+		return false;
+	}
+	if (brl_num_read_oplocks(br_lck) == num_read_oplocks) {
+		return true;
+	}
+
+	br_lck = brl_get_locks(talloc_tos(), fsp);
+	if (br_lck == NULL) {
+		return false;
+	}
+	brl_set_num_read_oplocks(br_lck, num_read_oplocks);
+	TALLOC_FREE(br_lck);
+	return true;
 }
 
 /****************************************************************************
@@ -167,44 +226,6 @@ bool remove_oplock(files_struct *fsp)
 		return False;
 	}
 
-	if (fsp->oplock_type == LEVEL_II_OPLOCK) {
-
-		/*
-		 * If we're the only LEVEL_II holder, we have to remove the
-		 * have_read_oplocks from the brlock entry
-		 */
-
-		struct share_mode_data *data = lck->data;
-		uint32_t i, num_level2;
-
-		num_level2 = 0;
-		for (i=0; i<data->num_share_modes; i++) {
-			if (data->share_modes[i].op_type == LEVEL_II_OPLOCK) {
-				num_level2 += 1;
-			}
-			if (num_level2 > 1) {
-				/*
-				 * No need to count them all...
-				 */
-				break;
-			}
-		}
-
-		if (num_level2 == 1) {
-			/*
-			 * That's only us. We are dropping that level2 oplock,
-			 * so remove the brlock flag.
-			 */
-			struct byte_range_lock *brl;
-
-			brl = brl_get_locks(talloc_tos(), fsp);
-			if (brl) {
-				brl_set_have_read_oplocks(brl, false);
-				TALLOC_FREE(brl);
-			}
-		}
-	}
-
 	ret = remove_share_oplock(lck, fsp);
 	if (!ret) {
 		DEBUG(0,("remove_oplock: failed to remove share oplock for "
@@ -213,6 +234,16 @@ bool remove_oplock(files_struct *fsp)
 			 file_id_string_tos(&fsp->file_id)));
 	}
 	release_file_oplock(fsp);
+	TALLOC_FREE(fsp->oplock_timeout);
+
+	ret = update_num_read_oplocks(fsp, lck);
+	if (!ret) {
+		DEBUG(0, ("%s: update_num_read_oplocks failed for "
+			 "file %s, %s, %s\n",
+			  __func__, fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+			 file_id_string_tos(&fsp->file_id)));
+	}
+
 	TALLOC_FREE(lck);
 	return ret;
 }
@@ -224,7 +255,6 @@ bool downgrade_oplock(files_struct *fsp)
 {
 	bool ret;
 	struct share_mode_lock *lck;
-	struct byte_range_lock *brl;
 
 	DEBUG(10, ("downgrade_oplock called for %s\n",
 		   fsp_str_dbg(fsp)));
@@ -244,17 +274,78 @@ bool downgrade_oplock(files_struct *fsp)
 	}
 
 	downgrade_file_oplock(fsp);
+	TALLOC_FREE(fsp->oplock_timeout);
 
-	brl = brl_get_locks(talloc_tos(), fsp);
-	if (brl != NULL) {
-		brl_set_have_read_oplocks(brl, true);
-		TALLOC_FREE(brl);
+	ret = update_num_read_oplocks(fsp, lck);
+	if (!ret) {
+		DEBUG(0,("%s: failed to downgrade share oplock "
+			 "for file %s, %s, file_id %s\n",
+			 __func__, fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+			 file_id_string_tos(&fsp->file_id)));
 	}
 
 	TALLOC_FREE(lck);
 	return ret;
 }
 
+struct downgrade_lease_fsps_state {
+	struct file_id id;
+	struct share_mode_lock *lck;
+	const struct smb2_lease_key *key;
+	uint32_t new_lease_state;
+	unsigned count;
+};
+
+static struct files_struct *downgrade_lease_fsps(struct files_struct *fsp,
+						 void *private_data)
+{
+	struct downgrade_lease_fsps_state *state =
+		(struct downgrade_lease_fsps_state *)private_data;
+	bool downgraded;
+
+	downgraded = fsp_lease_broken(state->lck, state->id, state->key, fsp,
+				      state->new_lease_state);
+	state->count += downgraded ? 1 : 0;
+	return NULL;
+}
+
+NTSTATUS downgrade_lease(struct smbd_server_connection *sconn,
+			 const struct file_id id,
+			 const struct smb2_lease_key *key,
+			 uint32_t lease_state)
+{
+	struct share_mode_lock *lck;
+	NTSTATUS status;
+
+	DEBUG(10, ("%s: Downgrading %s to %x\n", __func__,
+		   file_id_string_tos(&id), (unsigned)lease_state));
+
+	lck = get_existing_share_mode_lock(talloc_tos(), id);
+	if (lck == NULL) {
+		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+	}
+	status = downgrade_share_lease(sconn, lck, key, lease_state);
+
+	/*
+	 * This sucks. We have to reset fsp->sent_oplock_break on all fsps
+	 * that reference this lease.
+	 */
+	{
+		struct downgrade_lease_fsps_state state = {
+			.id = id, .lck = lck, .key = key,
+			.count = 0, .new_lease_state = lease_state
+		};
+
+		files_forall(sconn, downgrade_lease_fsps, &state);
+
+		/* Paranoia */
+		SMB_ASSERT(state.count > 0);
+	}
+
+	TALLOC_FREE(lck);
+	return status;
+}
+
 /****************************************************************************
  Set up an oplock break message.
 ****************************************************************************/
@@ -426,7 +517,6 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
 {
 	struct share_mode_entry msg;
 	files_struct *fsp;
-	bool break_to_level2 = False;
 	bool use_kernel;
 	struct smbd_server_connection *sconn =
 		talloc_get_type_abort(private_data,
@@ -467,23 +557,42 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
 		/*
 		 * Nothing to do anymore
 		 */
+		DEBUG(10, ("fsp->sent_oplock_break = %d\n",
+			   fsp->sent_oplock_break));
 		return;
 	}
 
-	if (break_to == fsp->oplock_type) {
-		DEBUG(3, ("Already downgraded oplock on %s: %s\n",
-			  file_id_string_tos(&fsp->file_id),
-			  fsp_str_dbg(fsp)));
-		return;
+	if (!(global_client_caps & CAP_LEVEL_II_OPLOCKS)) {
+		DEBUG(10, ("client_caps without level2 oplocks\n"));
+		break_to &= ~SMB2_LEASE_READ;
 	}
 
 	use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) && koplocks;
+	if (use_kernel && !(koplocks->flags & KOPLOCKS_LEVEL2_SUPPORTED)) {
+		DEBUG(10, ("Kernel oplocks don't allow level2\n"));
+		break_to &= ~SMB2_LEASE_READ;
+	}
+
+	if (!lp_level2_oplocks(SNUM(fsp->conn))) {
+		DEBUG(10, ("no level2 oplocks by config\n"));
+		break_to &= ~SMB2_LEASE_READ;
+	}
 
-	if ((global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
-	    (break_to != NO_OPLOCK) &&
-	    !(use_kernel && !(koplocks->flags & KOPLOCKS_LEVEL2_SUPPORTED)) &&
-	    lp_level2_oplocks(SNUM(fsp->conn))) {
-		break_to_level2 = True;
+	DEBUG(10, ("msg.op_type=%u, break_to=%u\n",
+		   (unsigned)msg.op_type, (unsigned)break_to));
+
+	if (fsp->oplock_type == NO_OPLOCK) {
+		DEBUG(3, ("Already downgraded oplock to none on %s: %s\n",
+			  file_id_string_tos(&fsp->file_id),
+			  fsp_str_dbg(fsp)));
+		return;
+	}
+	if ((break_to & SMB2_LEASE_READ) &&
+	    (fsp->oplock_type == LEVEL_II_OPLOCK)) {
+		DEBUG(3, ("Already downgraded oplock to level2 on %s: %s\n",
+			  file_id_string_tos(&fsp->file_id),
+			  fsp_str_dbg(fsp)));
+		return;
 	}
 
 	/* Need to wait before sending a break
@@ -493,21 +602,33 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
 	}
 
 	if (sconn->using_smb2) {
-		send_break_message_smb2(fsp, break_to_level2 ?
-			OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
+		send_break_message_smb2(fsp, break_to);
 	} else {
-		send_break_message_smb1(fsp, break_to_level2 ?
-			OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
+		send_break_message_smb1(fsp, (break_to & SMB2_LEASE_READ) ?
+					OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
 	}
 
-	if ((fsp->oplock_type == LEVEL_II_OPLOCK) && (break_to == NO_OPLOCK)) {
+	if ((fsp_lease_type(fsp) == SMB2_LEASE_READ) &&
+	    (break_to == SMB2_LEASE_NONE)) {
 		/*
 		 * This is an async break without a reply and thus no timeout
 		 */
-		remove_oplock(fsp);
+		if (fsp->oplock_type == LEASE_OPLOCK) {
+			/*
+			 * We must leave the lease around, it might be
+			 * upgraded later
+			 */
+			downgrade_lease(fsp->conn->sconn, fsp->file_id,
+					&fsp->lease->lease.lease_key,
+					break_to);
+		} else {
+			remove_oplock(fsp);
+		}
 		return;
 	}
-	fsp->sent_oplock_break = break_to_level2 ? LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
+	fsp->sent_oplock_break = (break_to & SMB2_LEASE_READ) ?
+		LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
+
 	add_oplock_timeout_handler(fsp);
 }
 
@@ -575,6 +696,7 @@ static void process_kernel_oplock_break(struct messaging_context *msg_ctx,
 struct break_to_none_state {
 	struct smbd_server_connection *sconn;
 	struct file_id id;
+	struct smb2_lease_key lease_key;
 };
 static void do_break_to_none(struct tevent_context *ctx,
 			     struct tevent_immediate *im,
@@ -593,6 +715,7 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp,
 	struct tevent_immediate *im;
 	struct break_to_none_state *state;
 	struct byte_range_lock *brl;
+	uint32_t num_read_oplocks;
 
 	/*
 	 * If this file is level II oplocked then we need
@@ -609,11 +732,23 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp,
 		return;
 	}
 
+	num_read_oplocks = 0;
+
 	brl = brl_get_locks_readonly(fsp);
-	if ((brl != NULL) && !brl_have_read_oplocks(brl)) {
+	if (brl != NULL) {
+		num_read_oplocks = brl_num_read_oplocks(brl);
+	}
+
+	DEBUG(10, ("num_read_oplocks = %"PRIu32"\n", num_read_oplocks));
+
+	if (num_read_oplocks == 0) {
 		DEBUG(10, ("No read oplocks around\n"));
 		return;
 	}
+	if ((num_read_oplocks == 1) && (fsp->oplock_type == LEASE_OPLOCK)) {
+		DEBUG(10, ("We're the only reader, don't break\n"));
+		return;
+	}
 
 	/*
 	 * When we get here we might have a brlock entry locked. Also
@@ -622,7 +757,7 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp,
 	 * anyway, so we postpone this into an immediate event.
 	 */
 
-	state = talloc(sconn, struct break_to_none_state);
+	state = talloc_zero(sconn, struct break_to_none_state);
 	if (state == NULL) {
 		DEBUG(1, ("talloc failed\n"));
 		return;
@@ -630,6 +765,13 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp,
 	state->sconn = sconn;
 	state->id = fsp->file_id;
 
+	if (fsp->oplock_type == LEASE_OPLOCK) {
+		state->lease_key = fsp->lease->lease.lease_key;
+		DEBUG(10, ("Breaking through lease key %"PRIu64"/%"PRIu64"\n",
+			   state->lease_key.data[0],
+			   state->lease_key.data[1]));
+	}
+
 	im = tevent_create_immediate(state);
 	if (im == NULL) {
 		DEBUG(1, ("tevent_create_immediate failed\n"));
@@ -639,14 +781,28 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp,
 	tevent_schedule_immediate(im, sconn->ev_ctx, do_break_to_none, state);
 }
 
+static void send_break_to_none(struct messaging_context *msg_ctx,
+			       const struct share_mode_entry *e)
+{
+	char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+
+	share_mode_entry_to_message(msg, e);
+	/* Overload entry->op_type */
+	SSVAL(msg, OP_BREAK_MSG_OP_TYPE_OFFSET, NO_OPLOCK);
+
+	messaging_send_buf(msg_ctx, e->pid, MSG_SMB_BREAK_REQUEST,
+			   (uint8 *)msg, sizeof(msg));
+}
+
 static void do_break_to_none(struct tevent_context *ctx,
 			     struct tevent_immediate *im,
 			     void *private_data)
 {
 	struct break_to_none_state *state = talloc_get_type_abort(
 		private_data, struct break_to_none_state);
-	int i;
+	uint32_t i;
 	struct share_mode_lock *lck;
+	struct share_mode_data *d;
 
 	lck = get_existing_share_mode_lock(talloc_tos(), state->id);
 	if (lck == NULL) {
@@ -654,15 +810,61 @@ static void do_break_to_none(struct tevent_context *ctx,
 			  __func__, file_id_string_tos(&state->id)));
 		goto done;
 	}
+	d = lck->data;
 
-	DEBUG(10,("%s: num_share_modes = %d\n", __func__,
-		  lck->data->num_share_modes ));
+	/*
+	 * Walk leases and oplocks separately: We have to send one break per
+	 * lease. If we have multiple share_mode_entry having a common lease,
+	 * we would break the lease twice if we don't walk the leases list
+	 * separately.
+	 */
 
-	for(i = 0; i < lck->data->num_share_modes; i++) {
-		struct share_mode_entry *share_entry = &lck->data->share_modes[i];
-		char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+	for (i=0; i<d->num_leases; i++) {
+		struct share_mode_oplock *l = &d->leases[i];
+		struct share_mode_entry *e;
+		uint32_t j;
 
-		if (!is_valid_share_mode_entry(share_entry)) {
+		if ((l->current_state & SMB2_LEASE_READ) == 0) {
+			continue;
+		}
+		if ((l->lease_key.data[0] == state->lease_key.data[0]) &&
+		    (l->lease_key.data[1] == state->lease_key.data[1])) {
+			DEBUG(10, ("Don't break our own lease\n"));
+			continue;
+		}
+
+		for (j=0; j<d->num_share_modes; j++) {
+			e = &d->share_modes[j];
+
+			if (!is_valid_share_mode_entry(e)) {
+				continue;
+			}
+			if (e->lease_idx == i) {
+				break;
+			}
+		}
+		if (j == d->num_share_modes) {
+			DEBUG(0, ("leases[%"PRIu32"] has no share mode\n",
+				  i));
+			continue;
+		}
+
+		DEBUG(10, ("Breaking lease# %"PRIu32" with share_entry# "
+			   "%"PRIu32"\n", i, j));
+
+		send_break_to_none(state->sconn->msg_ctx, e);
+	}
+
+	for(i = 0; i < d->num_share_modes; i++) {
+		struct share_mode_entry *e = &d->share_modes[i];
+
+		if (!is_valid_share_mode_entry(e)) {
+			continue;
+		}
+		if (e->op_type == LEASE_OPLOCK) {
+			/*
+			 * Took care of those in the loop above
+			 */
 			continue;
 		}
 
@@ -677,15 +879,15 @@ static void do_break_to_none(struct tevent_context *ctx,
 		 * NO_OPLOCK states. JRA.
 		 */
 
-		DEBUG(10,("%s: share_entry[%i]->op_type == %d\n", __func__,
-			  i, share_entry->op_type ));
+		DEBUG(10, ("%s: share_entry[%i]->op_type == %d\n", __func__,
+			   i, e->op_type ));
 
-		if (share_entry->op_type == NO_OPLOCK) {
+		if (e->op_type == NO_OPLOCK) {
 			continue;
 		}
 
 		/* Paranoia .... */
-		if (EXCLUSIVE_OPLOCK_TYPE(share_entry->op_type)) {
+		if (EXCLUSIVE_OPLOCK_TYPE(e->op_type)) {
 			DEBUG(0,("%s: PANIC. "
 				 "share mode entry %d is an exlusive "
 				 "oplock !\n", __func__, i ));
@@ -693,13 +895,7 @@ static void do_break_to_none(struct tevent_context *ctx,
 			abort();
 		}
 
-		share_mode_entry_to_message(msg, share_entry);
-		/* Overload entry->op_type */
-		SSVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET, NO_OPLOCK);
-
-		messaging_send_buf(state->sconn->msg_ctx, share_entry->pid,
-				   MSG_SMB_BREAK_REQUEST,
-				   (uint8 *)msg, sizeof(msg));
+		send_break_to_none(state->sconn->msg_ctx, e);
 	}
 
 	/* We let the message receivers handle removing the oplock state
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 68c2da2..613af8a 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -369,6 +369,9 @@ files_struct *file_find_dif(struct smbd_server_connection *sconn,
 files_struct *file_find_di_first(struct smbd_server_connection *sconn,
 				 struct file_id id);
 files_struct *file_find_di_next(files_struct *start_fsp);
+struct files_struct *file_find_one_fsp_from_lease_key(
+	struct smbd_server_connection *sconn,
+	const struct smb2_lease_key *lease_key);
 bool file_find_subpath(files_struct *dir_fsp);
 void file_sync_all(connection_struct *conn);
 void fsp_free(files_struct *fsp);
@@ -387,6 +390,7 @@ NTSTATUS file_name_hash(connection_struct *conn,
 			const char *name, uint32_t *p_name_hash);
 NTSTATUS fsp_set_smb_fname(struct files_struct *fsp,
 			   const struct smb_filename *smb_fname_in);
+uint32_t fsp_lease_type(struct files_struct *fsp);
 
 /* The following definitions come from smbd/ipc.c  */
 
@@ -609,6 +613,8 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
 				    const char *inherit_from_dir,
 				    const char *fname,
 				    SMB_STRUCT_STAT *psbuf);
+struct fsp_lease *find_fsp_lease(files_struct *new_fsp,
+				 const struct smb2_lease_key *key);
 bool is_stat_open(uint32 access_mask);
 struct deferred_open_record;
 bool is_deferred_open_async(const struct deferred_open_record *rec);
@@ -647,10 +653,18 @@ NTSTATUS get_relative_fid_filename(connection_struct *conn,
 
 /* The following definitions come from smbd/oplock.c  */
 
+uint32_t map_oplock_to_lease_type(uint16_t op_type);
+uint32_t get_lease_type(struct share_mode_data *d, struct share_mode_entry *e);
+bool update_num_read_oplocks(files_struct *fsp, struct share_mode_lock *lck);
+
 void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp);
 NTSTATUS set_file_oplock(files_struct *fsp);
 bool remove_oplock(files_struct *fsp);
 bool downgrade_oplock(files_struct *fsp);
+NTSTATUS downgrade_lease(struct smbd_server_connection *sconn,
+			 const struct file_id id,
+			 const struct smb2_lease_key *key,
+			 uint32_t lease_state);
 void contend_level2_oplocks_begin(files_struct *fsp,
 				  enum level2_contention_type type);
 void contend_level2_oplocks_end(files_struct *fsp,
diff --git a/source3/smbd/server.c b/source3/smbd/server.c
index 0d649e1..60394dd 100644
--- a/source3/smbd/server.c
+++ b/source3/smbd/server.c
@@ -47,6 +47,7 @@
 #include "../lib/util/pidfile.h"
 #include "lib/smbd_shim.h"
 #include "scavenger.h"
+#include "locking/leases_db.h"
 
 struct smbd_open_socket;
 struct smbd_child_pid;
@@ -1450,6 +1451,10 @@ extern void build_options(bool screen);
 	if (!locking_init())
 		exit_daemon("Samba cannot init locking", EACCES);
 
+	if (!leases_db_init(false)) {
+		exit_daemon("Samba cannot init leases", EACCES);
+	}
+
 	if (!smbd_parent_notify_init(NULL, msg_ctx, ev_ctx)) {
 		exit_daemon("Samba cannot init notification", EACCES);
 	}
diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c
index 5c079ec..a11cb5f 100644
--- a/source3/smbd/smb2_break.c
+++ b/source3/smbd/smb2_break.c
@@ -25,6 +25,9 @@
 #include "../libcli/smb/smb_common.h"
 #include "../lib/util/tevent_ntstatus.h"
 
+static NTSTATUS smbd_smb2_request_process_lease_break(
+	struct smbd_smb2_request *req);
+
 static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx,
 						      struct tevent_context *ev,
 						      struct smbd_smb2_request *smb2req,
@@ -45,6 +48,12 @@ NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req)
 	struct tevent_req *subreq;
 
 	status = smbd_smb2_request_verify_sizes(req, 0x18);
+	if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+		/*
+		 * Retry as a lease break
+		 */
+		return smbd_smb2_request_process_lease_break(req);
+	}
 	if (!NT_STATUS_IS_OK(status)) {
 		return smbd_smb2_request_error(req, status);
 	}
@@ -222,16 +231,163 @@ static NTSTATUS smbd_smb2_oplock_break_recv(struct tevent_req *req,
 	return NT_STATUS_OK;
 }
 
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+	TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+	struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+	uint32_t in_lease_state);
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+					   uint32_t *out_lease_state);
+
+
+static NTSTATUS smbd_smb2_request_process_lease_break(
+	struct smbd_smb2_request *req)
+{
+	NTSTATUS status;
+	const uint8_t *inbody;
+	struct smb2_lease_key in_lease_key;
+	uint32_t in_lease_state;
+	struct tevent_req *subreq;
+
+	status = smbd_smb2_request_verify_sizes(req, 0x24);
+	if (!NT_STATUS_IS_OK(status)) {
+		return smbd_smb2_request_error(req, status);
+	}
+
+	inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+	in_lease_key.data[0] = BVAL(inbody, 8);
+	in_lease_key.data[1] = BVAL(inbody, 16);
+	in_lease_state = IVAL(inbody, 24);
+
+	subreq = smbd_smb2_lease_break_send(req, req->sconn->ev_ctx, req,
+					    in_lease_key, in_lease_state);
+	if (subreq == NULL) {
+		return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+	}
+	tevent_req_set_callback(subreq, smbd_smb2_request_lease_break_done, req);
+
+	return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq)
+{
+	struct smbd_smb2_request *req = tevent_req_callback_data(
+		subreq, struct smbd_smb2_request);
+	const uint8_t *inbody;
+	struct smb2_lease_key in_lease_key;
+	uint32_t out_lease_state = 0;
+	DATA_BLOB outbody;
+	NTSTATUS status;
+	NTSTATUS error; /* transport error */
+
+	status = smbd_smb2_lease_break_recv(subreq, &out_lease_state);
+	TALLOC_FREE(subreq);
+	if (!NT_STATUS_IS_OK(status)) {
+		error = smbd_smb2_request_error(req, status);
+		if (!NT_STATUS_IS_OK(error)) {
+			smbd_server_connection_terminate(req->xconn,
+							 nt_errstr(error));
+			return;
+		}
+		return;
+	}
+
+	inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+	in_lease_key.data[0] = BVAL(inbody, 8);
+	in_lease_key.data[1] = BVAL(inbody, 16);
+
+	outbody = smbd_smb2_generate_outbody(req, 0x24);
+	if (outbody.data == NULL) {
+		error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+		if (!NT_STATUS_IS_OK(error)) {
+			smbd_server_connection_terminate(req->xconn,
+							 nt_errstr(error));
+			return;
+		}
+		return;
+	}
+
+	SSVAL(outbody.data, 0x00, 0x24);	/* struct size */
+	SSVAL(outbody.data, 0x02, 0);		/* reserved */
+	SIVAL(outbody.data, 0x04, 0);		/* flags, must be 0 */
+	SBVAL(outbody.data, 0x08, in_lease_key.data[0]);
+	SBVAL(outbody.data, 0x10, in_lease_key.data[1]);
+	SIVAL(outbody.data, 0x18, out_lease_state);
+	SBVAL(outbody.data, 0x1c, 0);           /* leaseduration, must be 0 */
+
+	error = smbd_smb2_request_done(req, outbody, NULL);
+	if (!NT_STATUS_IS_OK(error)) {
+		smbd_server_connection_terminate(req->xconn,
+						 nt_errstr(error));
+		return;
+	}
+}
+
+struct smbd_smb2_lease_break_state {
+	uint32_t lease_state;
+};
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+	TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+	struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+	uint32_t in_lease_state)
+{
+	struct tevent_req *req;
+	struct smbd_smb2_lease_break_state *state;
+	struct files_struct *fsp;
+	NTSTATUS status;
+
+	req = tevent_req_create(mem_ctx, &state,
+				struct smbd_smb2_lease_break_state);
+	if (req == NULL) {
+		return NULL;
+	}
+	state->lease_state = in_lease_state;
+
+	fsp = file_find_one_fsp_from_lease_key(smb2_req->sconn, &in_lease_key);
+	if (fsp == NULL) {
+		DEBUG(10, ("No fsp for lease key found\n"));
+		tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+		return tevent_req_post(req, ev);
+	}
+
+	status = downgrade_lease(smb2_req->sconn, fsp->file_id, &in_lease_key,
+				 in_lease_state);
+	if (tevent_req_nterror(req, status)) {
+		DEBUG(10, ("downgrade_lease returned %s\n",
+			   nt_errstr(status)));
+		return tevent_req_post(req, ev);
+	}
+	TALLOC_FREE(fsp->oplock_timeout);
+
+	tevent_req_done(req);
+	return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+					   uint32_t *out_lease_state)
+{
+	struct smbd_smb2_lease_break_state *state = tevent_req_data(
+		req, struct smbd_smb2_lease_break_state);
+	NTSTATUS status;
+
+	if (tevent_req_is_nterror(req, &status)) {
+		return status;
+	}
+	*out_lease_state = state->lease_state;
+	return NT_STATUS_OK;
+}
+
 /*********************************************************
  Create and send an asynchronous
  SMB2 OPLOCK_BREAK_NOTIFICATION.
 *********************************************************/
 
-void send_break_message_smb2(files_struct *fsp, int level)
+void send_break_message_smb2(files_struct *fsp, uint32_t break_to)
 {
-	uint8_t smb2_oplock_level = (level == OPLOCKLEVEL_II) ?
-				SMB2_OPLOCK_LEVEL_II :
-				SMB2_OPLOCK_LEVEL_NONE;
 	NTSTATUS status;
 	struct smbXsrv_connection *xconn = NULL;
 	struct smbXsrv_session *session = NULL;
@@ -257,7 +413,7 @@ void send_break_message_smb2(files_struct *fsp, int level)
 			"for file %s, %s, smb2 level %u session %llu not found\n",
 			fsp_str_dbg(fsp),
 			fsp_fnum_dbg(fsp),
-			(unsigned int)smb2_oplock_level,
+			(unsigned int)break_to,
 			(unsigned long long)fsp->vuid));
 		return;
 	}
@@ -266,13 +422,29 @@ void send_break_message_smb2(files_struct *fsp, int level)
 		"for file %s, %s, smb2 level %u\n",
 		fsp_str_dbg(fsp),
 		fsp_fnum_dbg(fsp),
-		(unsigned int)smb2_oplock_level ));
+		(unsigned int)break_to ));
+
+	if (fsp->oplock_type == LEASE_OPLOCK) {
+		bool no_ack;
 
-	status = smbd_smb2_send_oplock_break(xconn,
-					     session,
-					     fsp->conn->tcon,
-					     fsp->op,
-					     smb2_oplock_level);
+		no_ack = ((fsp->lease->lease.lease_state == SMB2_LEASE_READ) &&
+			  (break_to == SMB2_LEASE_NONE));
+
+		status = smbd_smb2_send_lease_break(
+			xconn, session, fsp->conn->tcon, 0,
+			no_ack ? 0 : SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED,
+			&fsp->lease->lease.lease_key,
+			fsp->lease->lease.lease_state, break_to);
+	} else {
+		uint8_t smb2_oplock_level;
+		smb2_oplock_level = (break_to & SMB2_LEASE_READ) ?
+			SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE;
+		status = smbd_smb2_send_oplock_break(xconn,
+						     session,
+						     fsp->conn->tcon,
+						     fsp->op,
+						     smb2_oplock_level);
+	}
 	if (!NT_STATUS_IS_OK(status)) {
 		smbd_server_connection_terminate(xconn,
 						 nt_errstr(status));
diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
index 48bc486..67d4fc0 100644
--- a/source3/smbd/smb2_create.c
+++ b/source3/smbd/smb2_create.c
@@ -25,6 +25,7 @@
 #include "smbd/globals.h"
 #include "../libcli/smb/smb_common.h"
 #include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_smb2_lease_struct.h"
 #include "../lib/util/tevent_ntstatus.h"
 #include "messages.h"
 
@@ -40,9 +41,7 @@ int map_smb2_oplock_levels_to_samba(uint8_t in_oplock_level)
 	case SMB2_OPLOCK_LEVEL_BATCH:
 		return BATCH_OPLOCK;
 	case SMB2_OPLOCK_LEVEL_LEASE:
-		DEBUG(2,("map_smb2_oplock_levels_to_samba: "
-			"LEASE_OPLOCK_REQUESTED\n"));
-		return NO_OPLOCK;
+		return LEASE_OPLOCK;
 	default:
 		DEBUG(2,("map_smb2_oplock_levels_to_samba: "
 			"unknown level %u\n",
@@ -59,6 +58,8 @@ static uint8_t map_samba_oplock_levels_to_smb2(int oplock_type)
 		return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
 	} else if (oplock_type == LEVEL_II_OPLOCK) {
 		return SMB2_OPLOCK_LEVEL_II;
+	} else if (oplock_type == LEASE_OPLOCK) {
+		return SMB2_OPLOCK_LEVEL_LEASE;
 	} else {
 		return SMB2_OPLOCK_LEVEL_NONE;
 	}
@@ -374,6 +375,62 @@ static void smbd_smb2_request_create_done(struct tevent_req *tsubreq)
 	}
 }
 
+static bool smb2_lease_key_valid(const struct smb2_lease_key *key)
+{
+	return ((key->data[0] != 0) || (key->data[1] != 0));
+}
+
+static NTSTATUS smbd_smb2_create_durable_lease_check(
+	const char *requested_filename, const struct files_struct *fsp,
+	const struct smb2_lease *lease_ptr)
+{
+	struct smb_filename *smb_fname = NULL;
+	NTSTATUS status;
+
+	if (lease_ptr == NULL) {
+		if (fsp->oplock_type != LEASE_OPLOCK) {
+			return NT_STATUS_OK;
+		}
+		DEBUG(10, ("Reopened file has lease, but no lease "
+			   "requested\n"));
+		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+	}
+
+	if (fsp->oplock_type != LEASE_OPLOCK) {
+		DEBUG(10, ("Lease requested, but reopened file has no "
+			   "lease\n"));
+		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+	}
+
+	if (!smb2_lease_key_equal(&lease_ptr->lease_key,
+				  &fsp->lease->lease.lease_key)) {
+		DEBUG(10, ("Different lease key requested than found "
+			   "in reopened file\n"));
+		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+	}
+
+	status = filename_convert(talloc_tos(), fsp->conn, false,
+				  requested_filename, UCF_PREP_CREATEFILE,
+				  NULL, &smb_fname);
+	if (!NT_STATUS_IS_OK(status)) {
+		DEBUG(10, ("filename_convert returned %s\n",
+			   nt_errstr(status)));
+		return status;
+	}
+
+	if (!strequal(fsp->fsp_name->base_name, smb_fname->base_name)) {
+		DEBUG(10, ("Lease requested for file %s, reopened file "
+			   "is named %s\n", smb_fname->base_name,
+			   fsp->fsp_name->base_name));
+		TALLOC_FREE(smb_fname);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	TALLOC_FREE(smb_fname);
+
+	return NT_STATUS_OK;
+}
+
 struct smbd_smb2_create_state {
 	struct smbd_smb2_request *smb2req;
 	struct smb_request *smb1req;
@@ -507,6 +564,11 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 			num_blobs_allowed = 1;
 		}
 
+		if (smb2_create_blob_find(&in_context_blobs,
+					  SMB2_CREATE_TAG_RQLS) != NULL) {
+			num_blobs_allowed += 1;
+		}
+
 		if (in_context_blobs.num_blobs != num_blobs_allowed) {
 			tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
 			return tevent_req_post(req, ev);
@@ -539,6 +601,11 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 
 		num_blobs_allowed = 1;
 
+		if (smb2_create_blob_find(&in_context_blobs,
+					  SMB2_CREATE_TAG_RQLS) != NULL) {
+			num_blobs_allowed += 1;
+		}
+
 		if (in_context_blobs.num_blobs != num_blobs_allowed) {
 			tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
 			return tevent_req_post(req, ev);
@@ -600,11 +667,15 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 		struct smb2_create_blob *qfid = NULL;
 		struct GUID _create_guid = GUID_zero();
 		struct GUID *create_guid = NULL;
+		struct smb2_create_blob *rqls = NULL;
 		bool update_open = false;
 		bool durable_requested = false;
 		uint32_t durable_timeout_msec = 0;
 		bool do_durable_reconnect = false;
 		uint64_t persistent_id = 0;
+		struct smb2_lease lease;
+		struct smb2_lease *lease_ptr;
+		ssize_t lease_len = -1;
 
 		exta = smb2_create_blob_find(&in_context_blobs,
 					     SMB2_CREATE_TAG_EXTA);
@@ -618,6 +689,8 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 					     SMB2_CREATE_TAG_TWRP);
 		qfid = smb2_create_blob_find(&in_context_blobs,
 					     SMB2_CREATE_TAG_QFID);
+		rqls = smb2_create_blob_find(&in_context_blobs,
+					     SMB2_CREATE_TAG_RQLS);
 
 		fname = talloc_strdup(state, in_name);
 		if (tevent_req_nomem(fname, req)) {
@@ -804,6 +877,39 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 			}
 		}
 
+		lease_ptr = NULL;
+
+		if (rqls) {
+			lease_len = smb2_lease_pull(
+				rqls->data.data, rqls->data.length, &lease);
+			if (lease_len == -1) {
+				tevent_req_nterror(
+					req, NT_STATUS_INVALID_PARAMETER);
+				return tevent_req_post(req, ev);
+			}
+			lease_ptr = &lease;
+
+			if (DEBUGLEVEL >= 10) {
+				DEBUG(10, ("Got lease request size %d\n",
+					   (int)lease_len));
+				NDR_PRINT_DEBUG(smb2_lease, lease_ptr);
+			}
+
+			if (!smb2_lease_key_valid(&lease.lease_key)) {
+				lease_ptr = NULL;
+				requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+			}
+
+			/* TODO client->connections ... */
+			if ((smb2req->sconn->client->connections->protocol <
+			     PROTOCOL_SMB3_00)
+			    && (lease_len == 52)) {
+				DEBUG(10, ("v2 lease key only for SMB3\n"));
+				lease_ptr = NULL;
+				requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+			}
+		}
+
 		/* these are ignored for SMB2 */
 		in_create_options &= ~(0x10);/* NTCREATEX_OPTIONS_SYNC_ALERT */
 		in_create_options &= ~(0x20);/* NTCREATEX_OPTIONS_ASYNC_ALERT */
@@ -863,6 +969,18 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 				return tevent_req_post(req, ev);
 			}
 
+			DEBUG(10, ("result->oplock_type=%u, lease_ptr==%p\n",
+				   (unsigned)result->oplock_type, lease_ptr));
+
+			status = smbd_smb2_create_durable_lease_check(
+				fname, result, lease_ptr);
+
+			if (!NT_STATUS_IS_OK(status)) {
+				close_file(smb1req, result, SHUTDOWN_CLOSE);
+				tevent_req_nterror(req, status);
+				return tevent_req_post(req, ev);
+			}
+
 			data_blob_free(&op->global->backend_cookie);
 			op->global->backend_cookie = new_cookie;
 
@@ -948,7 +1066,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 						     in_create_options,
 						     in_file_attributes,
 						     map_smb2_oplock_levels_to_samba(requested_oplock_level),
-						     NULL,
+						     lease_ptr,
 						     allocation_size,
 						     0, /* private_flags */
 						     sec_desc,
@@ -1003,7 +1121,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 		}
 
 		if (durable_requested &&
-		    BATCH_OPLOCK_TYPE(result->oplock_type))
+		    (fsp_lease_type(result) & SMB2_LEASE_HANDLE))
 		{
 			status = SMB_VFS_DURABLE_COOKIE(result,
 						op,
@@ -1086,6 +1204,29 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 				return tevent_req_post(req, ev);
 			}
 		}
+
+		if ((rqls != NULL) && (result->oplock_type == LEASE_OPLOCK)) {
+			uint8_t buf[52];
+
+			lease = result->lease->lease;
+			lease.lease_flags &=
+				SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET;
+
+			if (!smb2_lease_push(&lease, buf, lease_len)) {
+				tevent_req_nterror(
+					req, NT_STATUS_INTERNAL_ERROR);
+				return tevent_req_post(req, ev);
+			}
+
+			status = smb2_create_blob_add(
+				state, &out_context_blobs,
+				SMB2_CREATE_TAG_RQLS,
+				data_blob_const(buf, lease_len));
+			if (!NT_STATUS_IS_OK(status)) {
+				tevent_req_nterror(req, status);
+				return tevent_req_post(req, ev);
+			}
+		}
 	}
 
 	smb2req->compat_chain_fsp = smb1req->chain_fsp;
diff --git a/source3/smbd/smb2_negprot.c b/source3/smbd/smb2_negprot.c
index 6904972..a27ab0c 100644
--- a/source3/smbd/smb2_negprot.c
+++ b/source3/smbd/smb2_negprot.c
@@ -230,6 +230,10 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req)
 		capabilities |= SMB2_CAP_DFS;
 	}
 
+	if (protocol >= PROTOCOL_SMB2_10) {
+		capabilities |= SMB2_CAP_LEASING;
+	}
+
 	if ((protocol >= PROTOCOL_SMB2_24) &&
 	    (lp_smb_encrypt(-1) != SMB_SIGNING_OFF) &&
 	    (in_capabilities & SMB2_CAP_ENCRYPTION)) {
diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
index 689bfd7..cff10d8 100644
--- a/source3/smbd/smb2_server.c
+++ b/source3/smbd/smb2_server.c
@@ -2888,6 +2888,31 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn,
 	return smbd_smb2_send_break(xconn, session, tcon, body, sizeof(body));
 }
 
+NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn,
+				    struct smbXsrv_session *session,
+				    struct smbXsrv_tcon *tcon,
+				    uint16_t new_epoch,
+				    uint32_t lease_flags,
+				    struct smb2_lease_key *lease_key,
+				    uint32_t current_lease_state,
+				    uint32_t new_lease_state)
+{
+	uint8_t body[0x2c];
+
+	SSVAL(body, 0x00, sizeof(body));
+	SSVAL(body, 0x02, new_epoch);
+	SIVAL(body, 0x04, lease_flags);
+	SBVAL(body, 0x08, lease_key->data[0]);
+	SBVAL(body, 0x10, lease_key->data[1]);
+	SIVAL(body, 0x18, current_lease_state);
+	SIVAL(body, 0x1c, new_lease_state);
+	SIVAL(body, 0x20, 0);		/* BreakReason, MUST be 0 */
+	SIVAL(body, 0x24, 0);		/* AccessMaskHint, MUST be 0 */
+	SIVAL(body, 0x28, 0);		/* ShareMaskHint, MUST be 0 */
+
+	return smbd_smb2_send_break(sconn, session, tcon, body, sizeof(body));
+}
+
 static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
 {
 	NTSTATUS status;
diff --git a/source3/utils/status.c b/source3/utils/status.c
index 7bbcea5..08faca8 100644
--- a/source3/utils/status.c
+++ b/source3/utils/status.c
@@ -177,6 +177,8 @@ static void print_share_mode(const struct share_mode_entry *e,
 			d_printf("BATCH           ");
 		} else if (e->op_type & LEVEL_II_OPLOCK) {
 			d_printf("LEVEL_II        ");
+		} else if (e->op_type == LEASE_OPLOCK) {
+			d_printf("LEASE           ");
 		} else {
 			d_printf("NONE            ");
 		}
diff --git a/source3/wscript_build b/source3/wscript_build
index 54ba3a7..4328500 100755
--- a/source3/wscript_build
+++ b/source3/wscript_build
@@ -618,6 +618,7 @@ bld.SAMBA3_LIBRARY('smbd_base',
                    LIBAFS
                    RPC_SERVICE
                    NDR_SMBXSRV
+                   LEASES_DB
                    LIBASYS
                    sysquotas
                    ccan-hash
@@ -635,9 +636,14 @@ bld.SAMBA3_SUBSYSTEM('LOCKING',
                     deps='''
                     tdb_compat
                     talloc
+                    LEASES_DB
                     NDR_OPEN_FILES
                     FNAME_UTIL''')
 
+bld.SAMBA3_SUBSYSTEM('LEASES_DB',
+                    source='locking/leases_db.c',
+                    deps='NDR_LEASES_DB')
+
 if bld.CONFIG_GET("WITH_PROFILE"):
     bld.SAMBA3_SUBSYSTEM('PROFILE',
                          source='profile/profile.c',
diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c
index c3d63d1..7e1e6a2 100644
--- a/source4/torture/smb2/durable_open.c
+++ b/source4/torture/smb2/durable_open.c
@@ -25,6 +25,7 @@
 #include "libcli/smb2/smb2_calls.h"
 #include "../libcli/smb/smbXcli_base.h"
 #include "torture/torture.h"
+#include "torture/util.h"
 #include "torture/smb2/proto.h"
 #include "../libcli/smb/smbXcli_base.h"
 
@@ -53,7 +54,9 @@
 #define CHECK_CREATED(__io, __created, __attribute)			\
 	do {								\
 		CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
-		CHECK_VAL((__io)->out.alloc_size, 0);			\
+		if (!TARGET_IS_SAMBA3(tctx)) { \
+			CHECK_VAL((__io)->out.alloc_size, 0);		\
+		} \
 		CHECK_VAL((__io)->out.size, 0);				\
 		CHECK_VAL((__io)->out.file_attr, (__attribute));	\
 		CHECK_VAL((__io)->out.reserved2, 0);			\
diff --git a/source4/torture/smb2/lease.c b/source4/torture/smb2/lease.c
index 4326958..7ea2eab 100644
--- a/source4/torture/smb2/lease.c
+++ b/source4/torture/smb2/lease.c
@@ -25,6 +25,7 @@
 #include "libcli/smb2/smb2_calls.h"
 #include "torture/torture.h"
 #include "torture/smb2/proto.h"
+#include "torture/util.h"
 #include "libcli/smb/smbXcli_base.h"
 
 #define CHECK_VAL(v, correct) do { \
@@ -45,7 +46,9 @@
 #define CHECK_CREATED(__io, __created, __attribute)			\
 	do {								\
 		CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
-		CHECK_VAL((__io)->out.alloc_size, 0);			\
+		if (!TARGET_IS_SAMBA3(tctx)) { \
+			CHECK_VAL((__io)->out.alloc_size, 0);		\
+		} \
 		CHECK_VAL((__io)->out.size, 0);				\
 		CHECK_VAL((__io)->out.file_attr, (__attribute));	\
 		CHECK_VAL((__io)->out.reserved2, 0);			\
-- 
1.9.1


From 2e6d41a319e56ce0db3649f9450ac95730fca764 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Sun, 21 Sep 2014 09:32:25 +0200
Subject: [PATCH 05/22] smbd: use smbXsrv_connection as argument to
 smbd_smb2_send_lease_break()

Signed-off-by: Michael Adam <obnox at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/globals.h     | 2 +-
 source3/smbd/smb2_server.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h
index 39c537c..f297ff7 100644
--- a/source3/smbd/globals.h
+++ b/source3/smbd/globals.h
@@ -250,7 +250,7 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn,
 				     struct smbXsrv_tcon *tcon,
 				     struct smbXsrv_open *op,
 				     uint8_t oplock_level);
-NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn,
+NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_connection *xconn,
 				    struct smbXsrv_session *session,
 				    struct smbXsrv_tcon *tcon,
 				    uint16_t new_epoch,
diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
index cff10d8..36ac0d7 100644
--- a/source3/smbd/smb2_server.c
+++ b/source3/smbd/smb2_server.c
@@ -2888,7 +2888,7 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn,
 	return smbd_smb2_send_break(xconn, session, tcon, body, sizeof(body));
 }
 
-NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn,
+NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_connection *xconn,
 				    struct smbXsrv_session *session,
 				    struct smbXsrv_tcon *tcon,
 				    uint16_t new_epoch,
@@ -2910,7 +2910,7 @@ NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn,
 	SIVAL(body, 0x24, 0);		/* AccessMaskHint, MUST be 0 */
 	SIVAL(body, 0x28, 0);		/* ShareMaskHint, MUST be 0 */
 
-	return smbd_smb2_send_break(sconn, session, tcon, body, sizeof(body));
+	return smbd_smb2_send_break(xconn, session, tcon, body, sizeof(body));
 }
 
 static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
-- 
1.9.1


From e6bbdb429d73865fec4b915a1e01ce5371b57bb6 Mon Sep 17 00:00:00 2001
From: Michael Adam <obnox at samba.org>
Date: Sun, 21 Sep 2014 09:46:30 +0200
Subject: [PATCH 06/22] selftest: samba3.smb2.leases.v2_request_parent succeeds

Signed-off-by: Michael Adam <obnox at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 selftest/knownfail | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 5a9a865..d4cae25 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -193,7 +193,7 @@
 ^samba4.smb2.ioctl.copy_chunk_\w*\(dc\)	# not supported by s4 ntvfs server
 ^samba3.smb2.dir.one
 ^samba3.smb2.dir.modify
-^samba3.smb2.lease.v2_request
+^samba3.smb2.lease.v2_request\(.*\)$
 ^samba3.smb2.oplock.batch20
 ^samba3.smb2.oplock.stream1
 ^samba3.smb2.streams.rename
-- 
1.9.1


From db45604b4f8d29c60eb7298d504cc61559d8bde5 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Tue, 23 Sep 2014 02:28:38 +0200
Subject: [PATCH 07/22] s3: leases - epoch handling

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/open.c        |  6 +++---
 source3/smbd/oplock.c      | 23 +++++++++++++++++++++++
 source3/smbd/proto.h       |  3 +++
 source3/smbd/smb2_break.c  |  9 ++++++++-
 source3/smbd/smb2_create.c |  6 ++++++
 5 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 8d49030..ceb12c3 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1520,7 +1520,7 @@ static bool file_has_brlocks(files_struct *fsp)
 	return (brl_num_locks(br_lck) > 0);
 }
 
-static int find_share_mode_oplock(struct share_mode_data *d,
+int find_share_mode_oplock(struct share_mode_data *d,
 				  const struct GUID *client_guid,
 				  const struct smb2_lease_key *key)
 {
@@ -1669,8 +1669,8 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d,
 
 	d->leases[d->num_leases] = (struct share_mode_oplock) {
 		.client_guid = *client_guid,
-		.lease_key = lease->lease_key,
-		.epoch = lease->lease_epoch,
+		.lease_key = fsp->lease->lease.lease_key,
+		.epoch = fsp->lease->lease.lease_epoch,
 		.current_state = granted,
 	};
 
diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c
index c55fb3b..f80a3999 100644
--- a/source3/smbd/oplock.c
+++ b/source3/smbd/oplock.c
@@ -595,6 +595,29 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
 		return;
 	}
 
+	if (fsp->oplock_type == LEASE_OPLOCK) {
+		/* Need to increment the epoch */
+		struct share_mode_lock *lck;
+		int idx;
+
+		lck = get_existing_share_mode_lock(
+			talloc_tos(), fsp->file_id);
+
+		idx = find_share_mode_oplock(
+			lck->data,
+			&fsp->conn->sconn->client->connections->
+			   smb2.client.guid,
+			&fsp->lease->lease.lease_key);
+		if (idx != -1) {
+			struct share_mode_oplock *o;
+			o = &lck->data->leases[idx];
+			o->epoch += 1;
+			fsp->lease->lease.lease_epoch = o->epoch;
+		}
+
+		TALLOC_FREE(lck);
+	}
+
 	/* Need to wait before sending a break
 	   message if we sent ourselves this message. */
 	if (serverid_equal(&self, &src)) {
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 613af8a..08ad413 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -627,6 +627,9 @@ void msg_file_was_renamed(struct messaging_context *msg,
 			  DATA_BLOB *data);
 NTSTATUS open_streams_for_delete(connection_struct *conn,
 				 const char *fname);
+int find_share_mode_oplock(struct share_mode_data *d,
+			   const struct GUID *client_guid,
+			   const struct smb2_lease_key *key);
 NTSTATUS create_file_default(connection_struct *conn,
 			     struct smb_request *req,
 			     uint16_t root_dir_fid,
diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c
index a11cb5f..178dba9 100644
--- a/source3/smbd/smb2_break.c
+++ b/source3/smbd/smb2_break.c
@@ -426,12 +426,19 @@ void send_break_message_smb2(files_struct *fsp, uint32_t break_to)
 
 	if (fsp->oplock_type == LEASE_OPLOCK) {
 		bool no_ack;
+		uint16_t new_epoch;
 
 		no_ack = ((fsp->lease->lease.lease_state == SMB2_LEASE_READ) &&
 			  (break_to == SMB2_LEASE_NONE));
 
+		if (xconn->protocol >= PROTOCOL_SMB3_00) {
+			new_epoch = fsp->lease->lease.lease_epoch;
+		} else {
+			new_epoch = 0;
+		}
+
 		status = smbd_smb2_send_lease_break(
-			xconn, session, fsp->conn->tcon, 0,
+			xconn, session, fsp->conn->tcon, new_epoch,
 			no_ack ? 0 : SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED,
 			&fsp->lease->lease.lease_key,
 			fsp->lease->lease.lease_state, break_to);
diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
index 67d4fc0..abcfa1a 100644
--- a/source3/smbd/smb2_create.c
+++ b/source3/smbd/smb2_create.c
@@ -900,6 +900,12 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 				requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
 			}
 
+			if (smb2req->sconn->client->connections->protocol <
+			    PROTOCOL_SMB2_10) {
+				lease_ptr = NULL;
+				requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+			}
+
 			/* TODO client->connections ... */
 			if ((smb2req->sconn->client->connections->protocol <
 			     PROTOCOL_SMB3_00)
-- 
1.9.1


From 41d90b102072a4b4800f19c5979e4c01356e6308 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Tue, 23 Sep 2014 05:18:54 +0200
Subject: [PATCH 08/22] smbd: Add share_mode_forall->share_entry_forall

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/locking/proto.h                   | 4 ++--
 source3/locking/share_mode_lock.c         | 4 ++--
 source3/rpc_server/srvsvc/srv_srvsvc_nt.c | 8 ++++----
 source3/utils/status.c                    | 2 +-
 4 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index a347866..f7cd399 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -199,8 +199,8 @@ bool is_delete_on_close_set(struct share_mode_lock *lck, uint32_t name_hash);
 bool set_sticky_write_time(struct file_id fileid, struct timespec write_time);
 bool set_write_time(struct file_id fileid, struct timespec write_time);
 struct timespec get_share_mode_write_time(struct share_mode_lock *lck);
-int share_mode_forall(void (*fn)(const struct share_mode_entry *, const char *,
-				 const char *, void *),
+int share_entry_forall(void (*fn)(const struct share_mode_entry *, const char *,
+				  const char *, void *),
 		      void *private_data);
 bool share_mode_cleanup_disconnected(struct file_id id,
 				     uint64_t open_persistent_id);
diff --git a/source3/locking/share_mode_lock.c b/source3/locking/share_mode_lock.c
index ed0fb91..22a4474 100644
--- a/source3/locking/share_mode_lock.c
+++ b/source3/locking/share_mode_lock.c
@@ -499,8 +499,8 @@ static int traverse_fn(struct db_record *rec, void *_state)
  share mode system.
 ********************************************************************/
 
-int share_mode_forall(void (*fn)(const struct share_mode_entry *, const char *,
-				 const char *, void *),
+int share_entry_forall(void (*fn)(const struct share_mode_entry *,
+				  const char *, const char *, void *),
 		      void *private_data)
 {
 	struct forall_state state;
diff --git a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
index 855b8c7..d2f05f3 100644
--- a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
+++ b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
@@ -166,7 +166,7 @@ static WERROR net_enum_files(TALLOC_CTX *ctx,
 	f_enum_cnt.username = username;
 	f_enum_cnt.ctr3 = *ctr3;
 
-	share_mode_forall( enum_file_fn, (void *)&f_enum_cnt );
+	share_entry_forall( enum_file_fn, (void *)&f_enum_cnt );
 
 	*ctr3 = f_enum_cnt.ctr3;
 
@@ -867,7 +867,7 @@ static void net_count_files_for_all_sess(struct srvsvc_NetSessCtr1 *ctr1,
 	s_file_info.resume_handle = resume_handle;
 	s_file_info.num_entries = num_entries;
 
-	share_mode_forall(count_sess_files_fn, &s_file_info);
+	share_entry_forall(count_sess_files_fn, &s_file_info);
 }
 
 /*******************************************************************
@@ -984,7 +984,7 @@ static void count_share_opens(struct srvsvc_NetConnInfo1 *arr,
 	sfs.resp_entries = resp_entries;
 	sfs.total_entries = total_entries;
 
-	share_mode_forall(share_file_fn, &sfs);
+	share_entry_forall(share_file_fn, &sfs);
 }
 
 /****************************************************************************
@@ -2744,7 +2744,7 @@ WERROR _srvsvc_NetFileClose(struct pipes_struct *p,
 	r->out.result = WERR_BADFILE;
 	state.r = r;
 	state.msg_ctx = p->msg_ctx;
-	share_mode_forall(enum_file_close_fn, &state);
+	share_entry_forall(enum_file_close_fn, &state);
 	return r->out.result;
 }
 
diff --git a/source3/utils/status.c b/source3/utils/status.c
index 08faca8..cfc2bb6 100644
--- a/source3/utils/status.c
+++ b/source3/utils/status.c
@@ -528,7 +528,7 @@ int main(int argc, const char *argv[])
 			goto done;
 		}
 
-		result = share_mode_forall(print_share_mode, NULL);
+		result = share_entry_forall(print_share_mode, NULL);
 
 		if (result == 0) {
 			d_printf("No locked files\n");
-- 
1.9.1


From fff39011d946bcccbe2072060360b07f314d1f7d Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Tue, 23 Sep 2014 05:45:49 +0200
Subject: [PATCH 09/22] smbd: Introduce share_mode_forall

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/locking/proto.h           |  4 ++
 source3/locking/share_mode_lock.c | 81 +++++++++++++++++++++++++++------------
 2 files changed, 60 insertions(+), 25 deletions(-)

diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index f7cd399..23760f3 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -199,6 +199,10 @@ bool is_delete_on_close_set(struct share_mode_lock *lck, uint32_t name_hash);
 bool set_sticky_write_time(struct file_id fileid, struct timespec write_time);
 bool set_write_time(struct file_id fileid, struct timespec write_time);
 struct timespec get_share_mode_write_time(struct share_mode_lock *lck);
+int share_mode_forall(void (*fn)(struct file_id fid,
+				 const struct share_mode_data *data,
+				 void *private_data),
+		      void *private_data);
 int share_entry_forall(void (*fn)(const struct share_mode_entry *, const char *,
 				  const char *, void *),
 		      void *private_data);
diff --git a/source3/locking/share_mode_lock.c b/source3/locking/share_mode_lock.c
index 22a4474..f8054f4 100644
--- a/source3/locking/share_mode_lock.c
+++ b/source3/locking/share_mode_lock.c
@@ -441,14 +441,12 @@ struct share_mode_lock *fetch_share_mode_unlocked(TALLOC_CTX *mem_ctx,
 }
 
 struct forall_state {
-	void (*fn)(const struct share_mode_entry *entry,
-		   const char *sharepath,
-		   const char *fname,
+	void (*fn)(struct file_id fid, const struct share_mode_data *data,
 		   void *private_data);
 	void *private_data;
 };
 
-static int traverse_fn(struct db_record *rec, void *_state)
+static int share_mode_traverse_fn(struct db_record *rec, void *_state)
 {
 	struct forall_state *state = (struct forall_state *)_state;
 	uint32_t i;
@@ -457,13 +455,16 @@ static int traverse_fn(struct db_record *rec, void *_state)
 	DATA_BLOB blob;
 	enum ndr_err_code ndr_err;
 	struct share_mode_data *d;
+	struct file_id fid;
 
 	key = dbwrap_record_get_key(rec);
 	value = dbwrap_record_get_value(rec);
 
 	/* Ensure this is a locking_key record. */
-	if (key.dsize != sizeof(struct file_id))
+	if (key.dsize != sizeof(fid)) {
 		return 0;
+	}
+	memcpy(&fid, key.dptr, sizeof(fid));
 
 	d = talloc(talloc_tos(), struct share_mode_data);
 	if (d == NULL) {
@@ -485,11 +486,55 @@ static int traverse_fn(struct db_record *rec, void *_state)
 	}
 	for (i=0; i<d->num_share_modes; i++) {
 		d->share_modes[i].stale = false; /* [skip] in idl */
-		state->fn(&d->share_modes[i],
-			  d->servicepath, d->base_name,
-			  state->private_data);
 	}
+
+	state->fn(fid, d, state->private_data);
+
 	TALLOC_FREE(d);
+	return 0;
+}
+
+int share_mode_forall(void (*fn)(struct file_id fid,
+				 const struct share_mode_data *data,
+				 void *private_data),
+		      void *private_data)
+{
+	struct forall_state state = { .fn = fn, .private_data = private_data };
+	NTSTATUS status;
+	int count;
+
+	if (lock_db == NULL) {
+		return 0;
+	}
+
+	status = dbwrap_traverse_read(lock_db, share_mode_traverse_fn,
+				      &state, &count);
+	if (!NT_STATUS_IS_OK(status)) {
+		return -1;
+	}
+
+	return count;
+}
+
+struct share_entry_forall_state {
+	void (*fn)(const struct share_mode_entry *e,
+		   const char *service_path, const char *base_name,
+		   void *private_data);
+	void *private_data;
+};
+
+static int share_entry_traverse_fn(struct file_id fid,
+				   const struct share_mode_data *data,
+				   void *private_data)
+{
+	struct share_entry_forall_state *state = private_data;
+	uint32_t i;
+
+	for (i=0; i<data->num_share_modes; i++) {
+		state->fn(&data->share_modes[i],
+			  data->servicepath, data->base_name,
+			  state->private_data);
+	}
 
 	return 0;
 }
@@ -503,24 +548,10 @@ int share_entry_forall(void (*fn)(const struct share_mode_entry *,
 				  const char *, const char *, void *),
 		      void *private_data)
 {
-	struct forall_state state;
-	NTSTATUS status;
-	int count;
-
-	if (lock_db == NULL)
-		return 0;
-
-	state.fn = fn;
-	state.private_data = private_data;
+	struct share_entry_forall_state state = {
+		.fn = fn, .private_data = private_data };
 
-	status = dbwrap_traverse_read(lock_db, traverse_fn, (void *)&state,
-				      &count);
-
-	if (!NT_STATUS_IS_OK(status)) {
-		return -1;
-	} else {
-		return count;
-	}
+	return share_mode_forall(share_entry_traverse_fn, &state);
 }
 
 bool share_mode_cleanup_disconnected(struct file_id fid,
-- 
1.9.1


From 446877c3f9f30854e35759bc252a65cd06eebf20 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Tue, 23 Sep 2014 18:49:46 +0200
Subject: [PATCH 10/22] First test for NT_STATUS_INVALID_OPLOCK_PROTOCOL, then
 for in_oplock_level being reasonable

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/smb2_break.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c
index 178dba9..abe6709 100644
--- a/source3/smbd/smb2_break.c
+++ b/source3/smbd/smb2_break.c
@@ -61,11 +61,6 @@ NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req)
 
 	in_oplock_level		= CVAL(inbody, 0x02);
 
-	if (in_oplock_level != SMB2_OPLOCK_LEVEL_NONE &&
-			in_oplock_level != SMB2_OPLOCK_LEVEL_II) {
-		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
-	}
-
 	/* 0x03 1 bytes reserved */
 	/* 0x04 4 bytes reserved */
 	in_file_id_persistent		= BVAL(inbody, 0x08);
@@ -76,6 +71,17 @@ NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req)
 		return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
 	}
 
+	/* Are we awaiting a break message ? */
+	if (in_fsp->oplock_timeout == NULL) {
+		return smbd_smb2_request_error(
+			req, NT_STATUS_INVALID_OPLOCK_PROTOCOL);
+	}
+
+	if (in_oplock_level != SMB2_OPLOCK_LEVEL_NONE &&
+	    in_oplock_level != SMB2_OPLOCK_LEVEL_II) {
+		return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+	}
+
 	subreq = smbd_smb2_oplock_break_send(req, req->sconn->ev_ctx,
 					     req, in_fsp, in_oplock_level);
 	if (subreq == NULL) {
@@ -186,12 +192,6 @@ static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx,
 		fsp_str_dbg(fsp),
 		fsp_fnum_dbg(fsp)));
 
-	/* Are we awaiting a break message ? */
-	if (fsp->oplock_timeout == NULL) {
-		tevent_req_nterror(req, NT_STATUS_INVALID_OPLOCK_PROTOCOL);
-		return tevent_req_post(req, ev);
-	}
-
 	if ((fsp->sent_oplock_break == BREAK_TO_NONE_SENT) ||
 			(break_to_none)) {
 		result = remove_oplock(fsp);
-- 
1.9.1


From 3288a474afd7126fa84681255c4df88f1ea2c9c1 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Tue, 23 Sep 2014 22:56:41 +0200
Subject: [PATCH 11/22] s3: leases - Only increment epoch for V2 leases

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 libcli/smb/smb2_lease.c          | 1 +
 librpc/idl/smb2_lease_struct.idl | 1 +
 source3/smbd/open.c              | 4 +++-
 source3/smbd/oplock.c            | 3 ++-
 source3/smbd/smb2_create.c       | 4 ++--
 5 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/libcli/smb/smb2_lease.c b/libcli/smb/smb2_lease.c
index 70dd3d4..41eafc9 100644
--- a/libcli/smb/smb2_lease.c
+++ b/libcli/smb/smb2_lease.c
@@ -43,6 +43,7 @@ ssize_t smb2_lease_pull(const uint8_t *buf, size_t len,
 	lease->lease_state = IVAL(buf, 16);
 	lease->lease_flags = IVAL(buf, 20);
 	lease->lease_duration = BVAL(buf, 24);
+	lease->lease_version = version;
 
 	switch (version) {
 	case 1:
diff --git a/librpc/idl/smb2_lease_struct.idl b/librpc/idl/smb2_lease_struct.idl
index be80d14..5ccd8a3 100644
--- a/librpc/idl/smb2_lease_struct.idl
+++ b/librpc/idl/smb2_lease_struct.idl
@@ -28,6 +28,7 @@ interface smb2_lease_struct
 		uint32 lease_flags;
 		hyper lease_duration;	/* should be 0 */
 		smb2_lease_key parent_lease_key;
+		uint16 lease_version;
 		uint16 lease_epoch;
 	} smb2_lease;
 };
\ No newline at end of file
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index ceb12c3..70eeed8 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1663,7 +1663,9 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d,
 	fsp->lease->ref_count = 1;
 	fsp->lease->lease = *lease;
 	fsp->lease->lease.lease_state = granted;
-	fsp->lease->lease.lease_epoch += 1;
+	if (fsp->lease->lease.lease_version > 1) {
+		fsp->lease->lease.lease_epoch += 1;
+	}
 
 	*p_lease_idx = d->num_leases;
 
diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c
index f80a3999..d42058c 100644
--- a/source3/smbd/oplock.c
+++ b/source3/smbd/oplock.c
@@ -595,7 +595,8 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
 		return;
 	}
 
-	if (fsp->oplock_type == LEASE_OPLOCK) {
+	if ((fsp->oplock_type == LEASE_OPLOCK) &&
+	    (fsp->lease->lease.lease_version != 1)) {
 		/* Need to increment the epoch */
 		struct share_mode_lock *lck;
 		int idx;
diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
index abcfa1a..38ef04c 100644
--- a/source3/smbd/smb2_create.c
+++ b/source3/smbd/smb2_create.c
@@ -908,8 +908,8 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
 
 			/* TODO client->connections ... */
 			if ((smb2req->sconn->client->connections->protocol <
-			     PROTOCOL_SMB3_00)
-			    && (lease_len == 52)) {
+			     PROTOCOL_SMB3_00) &&
+			    (lease.lease_version != 1)) {
 				DEBUG(10, ("v2 lease key only for SMB3\n"));
 				lease_ptr = NULL;
 				requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
-- 
1.9.1


From e3701f58764c2a9d78a4d80f84c8023edcfb196a Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Tue, 23 Sep 2014 23:34:14 +0200
Subject: [PATCH 12/22] s3: leases - break to none with FILE_OVERWRITE

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/open.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 70eeed8..869869a 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1446,6 +1446,7 @@ static bool delay_for_oplock(files_struct *fsp,
 	switch (create_disposition) {
 	case FILE_SUPERSEDE:
 	case FILE_OVERWRITE_IF:
+	case FILE_OVERWRITE:
 		will_overwrite = true;
 		break;
 	default:
-- 
1.9.1


From b8dd0df38972d2cd3411ac9058895fef6bda44d4 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Wed, 24 Sep 2014 20:46:15 +0200
Subject: [PATCH 13/22] s3: leases - allow early return for share_mode_forall

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/locking/proto.h                   | 10 +++----
 source3/locking/share_mode_lock.c         | 38 ++++++++++++++-----------
 source3/rpc_server/srvsvc/srv_srvsvc_nt.c | 46 +++++++++++++++++--------------
 source3/utils/status.c                    |  8 +++---
 4 files changed, 57 insertions(+), 45 deletions(-)

diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index 23760f3..94e9b8f 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -199,12 +199,12 @@ bool is_delete_on_close_set(struct share_mode_lock *lck, uint32_t name_hash);
 bool set_sticky_write_time(struct file_id fileid, struct timespec write_time);
 bool set_write_time(struct file_id fileid, struct timespec write_time);
 struct timespec get_share_mode_write_time(struct share_mode_lock *lck);
-int share_mode_forall(void (*fn)(struct file_id fid,
-				 const struct share_mode_data *data,
-				 void *private_data),
+int share_mode_forall(int (*fn)(struct file_id fid,
+				const struct share_mode_data *data,
+				void *private_data),
 		      void *private_data);
-int share_entry_forall(void (*fn)(const struct share_mode_entry *, const char *,
-				  const char *, void *),
+int share_entry_forall(int (*fn)(const struct share_mode_entry *, const char *,
+				 const char *, void *),
 		      void *private_data);
 bool share_mode_cleanup_disconnected(struct file_id id,
 				     uint64_t open_persistent_id);
diff --git a/source3/locking/share_mode_lock.c b/source3/locking/share_mode_lock.c
index f8054f4..5c5f3ed 100644
--- a/source3/locking/share_mode_lock.c
+++ b/source3/locking/share_mode_lock.c
@@ -441,8 +441,8 @@ struct share_mode_lock *fetch_share_mode_unlocked(TALLOC_CTX *mem_ctx,
 }
 
 struct forall_state {
-	void (*fn)(struct file_id fid, const struct share_mode_data *data,
-		   void *private_data);
+	int (*fn)(struct file_id fid, const struct share_mode_data *data,
+		  void *private_data);
 	void *private_data;
 };
 
@@ -456,6 +456,7 @@ static int share_mode_traverse_fn(struct db_record *rec, void *_state)
 	enum ndr_err_code ndr_err;
 	struct share_mode_data *d;
 	struct file_id fid;
+	int ret;
 
 	key = dbwrap_record_get_key(rec);
 	value = dbwrap_record_get_value(rec);
@@ -488,15 +489,15 @@ static int share_mode_traverse_fn(struct db_record *rec, void *_state)
 		d->share_modes[i].stale = false; /* [skip] in idl */
 	}
 
-	state->fn(fid, d, state->private_data);
+	ret = state->fn(fid, d, state->private_data);
 
 	TALLOC_FREE(d);
-	return 0;
+	return ret;
 }
 
-int share_mode_forall(void (*fn)(struct file_id fid,
-				 const struct share_mode_data *data,
-				 void *private_data),
+int share_mode_forall(int (*fn)(struct file_id fid,
+				const struct share_mode_data *data,
+				void *private_data),
 		      void *private_data)
 {
 	struct forall_state state = { .fn = fn, .private_data = private_data };
@@ -517,9 +518,9 @@ int share_mode_forall(void (*fn)(struct file_id fid,
 }
 
 struct share_entry_forall_state {
-	void (*fn)(const struct share_mode_entry *e,
-		   const char *service_path, const char *base_name,
-		   void *private_data);
+	int (*fn)(const struct share_mode_entry *e,
+		  const char *service_path, const char *base_name,
+		  void *private_data);
 	void *private_data;
 };
 
@@ -531,9 +532,14 @@ static int share_entry_traverse_fn(struct file_id fid,
 	uint32_t i;
 
 	for (i=0; i<data->num_share_modes; i++) {
-		state->fn(&data->share_modes[i],
-			  data->servicepath, data->base_name,
-			  state->private_data);
+		int ret;
+
+		ret = state->fn(&data->share_modes[i],
+				data->servicepath, data->base_name,
+				state->private_data);
+		if (ret != 0) {
+			return ret;
+		}
 	}
 
 	return 0;
@@ -544,9 +550,9 @@ static int share_entry_traverse_fn(struct file_id fid,
  share mode system.
 ********************************************************************/
 
-int share_entry_forall(void (*fn)(const struct share_mode_entry *,
-				  const char *, const char *, void *),
-		      void *private_data)
+int share_entry_forall(int (*fn)(const struct share_mode_entry *,
+				 const char *, const char *, void *),
+		       void *private_data)
 {
 	struct share_entry_forall_state state = {
 		.fn = fn, .private_data = private_data };
diff --git a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
index d2f05f3..0278182 100644
--- a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
+++ b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
@@ -79,9 +79,9 @@ struct share_conn_stat {
 /*******************************************************************
 ********************************************************************/
 
-static void enum_file_fn( const struct share_mode_entry *e,
-                          const char *sharepath, const char *fname,
-			  void *private_data )
+static int enum_file_fn( const struct share_mode_entry *e,
+			 const char *sharepath, const char *fname,
+			 void *private_data )
 {
 	struct file_enum_count *fenum =
 		(struct file_enum_count *)private_data;
@@ -98,21 +98,21 @@ static void enum_file_fn( const struct share_mode_entry *e,
 	/* If the pid was not found delete the entry from connections.tdb */
 
 	if ( !process_exists(e->pid) ) {
-		return;
+		return 0;
 	}
 
 	username = uidtoname(e->uid);
 
 	if ((fenum->username != NULL)
 	    && !strequal(username, fenum->username)) {
-		return;
+		return 0;
 	}
 
 	f = talloc_realloc(fenum->ctx, fenum->ctr3->array,
 				 struct srvsvc_NetFileInfo3, i+1);
 	if ( !f ) {
 		DEBUG(0,("conn_enum_fn: realloc failed for %d items\n", i+1));
-		return;
+		return 0;
 	}
 	fenum->ctr3->array = f;
 
@@ -133,7 +133,7 @@ static void enum_file_fn( const struct share_mode_entry *e,
 				sharepath, fname );
 	}
 	if (!fullpath) {
-		return;
+		return 0;
 	}
 	string_replace( fullpath, '/', '\\' );
 
@@ -150,6 +150,8 @@ static void enum_file_fn( const struct share_mode_entry *e,
 	fenum->ctr3->array[i].user		= username;
 
 	fenum->ctr3->count++;
+
+	return 0;
 }
 
 /*******************************************************************
@@ -826,9 +828,9 @@ static WERROR init_srv_sess_info_0(struct pipes_struct *p,
  * find out the session on which this file is open and bump up its count
  **********************************************************************/
 
-static void count_sess_files_fn(const struct share_mode_entry *e,
-				const char *sharepath, const char *fname,
-				void *data)
+static int count_sess_files_fn(const struct share_mode_entry *e,
+			       const char *sharepath, const char *fname,
+			       void *data)
 {
 	struct sess_file_info *info = data;
 	uint32_t rh = info->resume_handle;
@@ -846,9 +848,10 @@ static void count_sess_files_fn(const struct share_mode_entry *e,
 		     serverid_equal(&e->pid, &sess->pid)) {
 
 			info->ctr->array[i].num_open++;
-			return;
+			return 0;
 		}
 	}
+	return 0;
 }
 
 /*******************************************************************
@@ -950,9 +953,9 @@ static WERROR init_srv_sess_info_1(struct pipes_struct *p,
  find the share connection on which this open exists.
  ********************************************************************/
 
-static void share_file_fn(const struct share_mode_entry *e,
-			  const char *sharepath, const char *fname,
-			  void *data)
+static int share_file_fn(const struct share_mode_entry *e,
+			 const char *sharepath, const char *fname,
+			 void *data)
 {
 	struct share_file_stat *sfs = data;
 	uint32_t i;
@@ -962,10 +965,11 @@ static void share_file_fn(const struct share_mode_entry *e,
 		for (i=0; i < sfs->resp_entries; i++) {
 			if (serverid_equal(&e->pid, &sfs->svrid_arr[offset + i])) {
 				sfs->netconn_arr[i].num_open ++;
-				return;
+				return 0;
 			}
 		}
 	}
+	return 0;
 }
 
 /*******************************************************************
@@ -2690,9 +2694,9 @@ struct enum_file_close_state {
 	struct messaging_context *msg_ctx;
 };
 
-static void enum_file_close_fn( const struct share_mode_entry *e,
-                          const char *sharepath, const char *fname,
-			  void *private_data )
+static int enum_file_close_fn( const struct share_mode_entry *e,
+			       const char *sharepath, const char *fname,
+			       void *private_data )
 {
 	char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
 	struct enum_file_close_state *state =
@@ -2700,11 +2704,11 @@ static void enum_file_close_fn( const struct share_mode_entry *e,
 	uint32_t fid = (((uint32_t)(procid_to_pid(&e->pid))<<16) | e->share_file_id);
 
 	if (fid != state->r->in.fid) {
-		return; /* Not this file. */
+		return 0; /* Not this file. */
 	}
 
 	if (!process_exists(e->pid) ) {
-		return;
+		return 0;
 	}
 
 	/* Ok - send the close message. */
@@ -2718,6 +2722,8 @@ static void enum_file_close_fn( const struct share_mode_entry *e,
 		messaging_send_buf(state->msg_ctx,
 				e->pid, MSG_SMB_CLOSE_FILE,
 				(uint8 *)msg, sizeof(msg)));
+
+	return 0;
 }
 
 /********************************************************************
diff --git a/source3/utils/status.c b/source3/utils/status.c
index cfc2bb6..0bcc2af 100644
--- a/source3/utils/status.c
+++ b/source3/utils/status.c
@@ -115,10 +115,10 @@ static bool Ucrit_addPid( struct server_id pid )
 	return True;
 }
 
-static void print_share_mode(const struct share_mode_entry *e,
-			     const char *sharepath,
-			     const char *fname,
-			     void *dummy)
+static int print_share_mode(const struct share_mode_entry *e,
+			    const char *sharepath,
+			    const char *fname,
+			    void *dummy)
 {
 	static int count;
 
-- 
1.9.1


From ac1b9af69486a0b9e6d29c0a61796a1acf5722eb Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Thu, 25 Sep 2014 01:30:33 +0200
Subject: [PATCH 14/22] smbd: Don't rename a dir with files open underneath

This is an EXPENSIVE check. We'll have to guard this with an option

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/dir.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 116 insertions(+)

diff --git a/source3/smbd/dir.c b/source3/smbd/dir.c
index e60bc2c..29184a3 100644
--- a/source3/smbd/dir.c
+++ b/source3/smbd/dir.c
@@ -25,6 +25,7 @@
 #include "libcli/security/security.h"
 #include "lib/util/bitmap.h"
 #include "../lib/util/memcache.h"
+#include "../librpc/gen_ndr/open_files.h"
 
 /*
    This module implements directory related functions for Samba.
@@ -1809,6 +1810,113 @@ bool SearchDir(struct smb_Dir *dirp, const char *name, long *poffset)
 	return False;
 }
 
+struct files_below_forall_state {
+	char *dirpath;
+	size_t dirpath_len;
+	int (*fn)(struct file_id fid, const struct share_mode_data *data,
+		  void *private_data);
+	void *private_data;
+};
+
+static int files_below_forall_fn(struct file_id fid,
+				 const struct share_mode_data *data,
+				 void *private_data)
+{
+	struct files_below_forall_state *state = private_data;
+	char tmpbuf[PATH_MAX];
+	char *fullpath, *to_free;
+	size_t len;
+
+	len = full_path_tos(data->servicepath, data->base_name,
+			    tmpbuf, sizeof(tmpbuf),
+			    &fullpath, &to_free);
+	if (len == -1) {
+		return 0;
+	}
+	if (state->dirpath_len >= len) {
+		/*
+		 * Filter files above dirpath
+		 */
+		return 0;
+	}
+	if (fullpath[state->dirpath_len] != '/') {
+		/*
+		 * Filter file that don't have a path separator at the end of
+		 * dirpath's length
+		 */
+		return 0;
+	}
+
+	if (memcmp(state->dirpath, fullpath, len) != 0) {
+		/*
+		 * Not a parent
+		 */
+		return 0;
+	}
+
+	return state->fn(fid, data, private_data);
+}
+
+static int files_below_forall(connection_struct *conn,
+			      const struct smb_filename *dir_name,
+			      int (*fn)(struct file_id fid,
+					const struct share_mode_data *data,
+					void *private_data),
+			      void *private_data)
+{
+	struct files_below_forall_state state = {};
+	int ret;
+	char tmpbuf[PATH_MAX];
+	char *to_free;
+
+	state.dirpath_len = full_path_tos(conn->connectpath,
+					  dir_name->base_name,
+					  tmpbuf, sizeof(tmpbuf),
+					  &state.dirpath, &to_free);
+	if (state.dirpath_len == -1) {
+		return -1;
+
+	}
+
+	ret = share_mode_forall(files_below_forall_fn, &state);
+	TALLOC_FREE(to_free);
+	return ret;
+}
+
+struct have_file_open_below_state {
+	bool found_one;
+};
+
+static int have_file_open_below_fn(struct file_id fid,
+				   const struct share_mode_data *data,
+				   void *private_data)
+{
+	struct have_file_open_below_state *state = private_data;
+	state->found_one = true;
+	return 1;
+}
+
+static bool have_file_open_below(connection_struct *conn,
+				 const struct smb_filename *name)
+{
+	struct have_file_open_below_state state = {};
+	int ret;
+
+	if (!VALID_STAT(name->st)) {
+		return false;
+	}
+	if (!S_ISDIR(name->st.st_ex_mode)) {
+		return false;
+	}
+
+	ret = files_below_forall(conn, name, have_file_open_below_fn,	 &state);
+	if (ret == -1) {
+		return false;
+	}
+
+	return state.found_one;
+}
+
 /*****************************************************************
  Is this directory empty ?
 *****************************************************************/
@@ -1854,5 +1962,13 @@ NTSTATUS can_delete_directory_fsp(files_struct *fsp)
 	TALLOC_FREE(talloced);
 	TALLOC_FREE(dir_hnd);
 
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
+
+	if (have_file_open_below(fsp->conn, fsp->fsp_name)) {
+		return NT_STATUS_ACCESS_DENIED;
+	}
+
 	return status;
 }
-- 
1.9.1


From 84b84526009cf9a8dcfa34bdf24f7913f80bea91 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Thu, 25 Sep 2014 01:32:00 +0200
Subject: [PATCH 15/22] s3: smbd - test rename dir deny with open files

Signed-off-by: Volker Lendecke <vl at samba.org>
Reviewed-by: Jeremy Allison <jra at samba.org>
---
 source4/torture/smb2/rename.c | 97 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/source4/torture/smb2/rename.c b/source4/torture/smb2/rename.c
index 9d0f4e1..07bdabd 100644
--- a/source4/torture/smb2/rename.c
+++ b/source4/torture/smb2/rename.c
@@ -928,6 +928,99 @@ done:
 	return ret;
 }
 
+static bool torture_smb2_rename_dir_openfile(struct torture_context *torture,
+					     struct smb2_tree *tree1)
+{
+	bool ret = true;
+	NTSTATUS status;
+	union smb_open io;
+	union smb_close cl;
+	union smb_setfileinfo sinfo;
+	struct smb2_handle d1, h1;
+
+	smb2_deltree(tree1, BASEDIR);
+	smb2_util_rmdir(tree1, BASEDIR);
+
+	torture_comment(torture, "Creating base directory\n");
+
+	ZERO_STRUCT(io.smb2);
+	io.generic.level = RAW_OPEN_SMB2;
+	io.smb2.in.create_flags = 0;
+	io.smb2.in.desired_access = 0x0017019f;
+	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
+	io.smb2.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY;
+	io.smb2.in.share_access = 0;
+	io.smb2.in.alloc_size = 0;
+	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
+	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+	io.smb2.in.security_flags = 0;
+	io.smb2.in.fname = BASEDIR;
+
+	status = smb2_create(tree1, torture, &(io.smb2));
+	CHECK_STATUS(status, NT_STATUS_OK);
+	d1 = io.smb2.out.file.handle;
+
+	torture_comment(torture, "Creating test file\n");
+
+	ZERO_STRUCT(io.smb2);
+	io.generic.level = RAW_OPEN_SMB2;
+	io.smb2.in.create_flags = 0;
+	io.smb2.in.desired_access = 0x0017019f;
+	io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
+	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	io.smb2.in.share_access = 0;
+	io.smb2.in.alloc_size = 0;
+	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
+	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+	io.smb2.in.security_flags = 0;
+	io.smb2.in.fname = BASEDIR "\\file.txt";
+
+	status = smb2_create(tree1, torture, &(io.smb2));
+	CHECK_STATUS(status, NT_STATUS_OK);
+	h1 = io.smb2.out.file.handle;
+
+	torture_comment(torture, "Renaming directory\n");
+
+	ZERO_STRUCT(sinfo);
+	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
+	sinfo.rename_information.in.file.handle = d1;
+	sinfo.rename_information.in.overwrite = 0;
+	sinfo.rename_information.in.root_fid = 0;
+	sinfo.rename_information.in.new_name =
+		BASEDIR "-new";
+	status = smb2_setinfo_file(tree1, &sinfo);
+	CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED);
+
+	torture_comment(torture, "Closing directory\n");
+
+	ZERO_STRUCT(cl.smb2);
+	cl.smb2.level = RAW_CLOSE_SMB2;
+	cl.smb2.in.file.handle = d1;
+	status = smb2_close(tree1, &(cl.smb2));
+	CHECK_STATUS(status, NT_STATUS_OK);
+	ZERO_STRUCT(d1);
+
+	torture_comment(torture, "Closing test file\n");
+
+	cl.smb2.in.file.handle = h1;
+	status = smb2_close(tree1, &(cl.smb2));
+	CHECK_STATUS(status, NT_STATUS_OK);
+	ZERO_STRUCT(h1);
+
+done:
+
+	torture_comment(torture, "Cleaning up\n");
+
+	if (h1.data) {
+		ZERO_STRUCT(cl.smb2);
+		cl.smb2.level = RAW_CLOSE_SMB2;
+		cl.smb2.in.file.handle = h1;
+		status = smb2_close(tree1, &(cl.smb2));
+	}
+	smb2_deltree(tree1, BASEDIR);
+	return ret;
+}
+
 struct rename_one_dir_cycle_state {
 	struct tevent_context *ev;
 	struct smb2_tree *tree;
@@ -1336,6 +1429,10 @@ struct torture_suite *torture_smb2_rename_init(void)
 		"msword",
 		torture_smb2_rename_msword);
 
+	torture_suite_add_1smb2_test(
+		suite, "rename_dir_openfile",
+		torture_smb2_rename_dir_openfile);
+
 	torture_suite_add_1smb2_test(suite,
 		"rename_dir_bench",
 		torture_smb2_rename_dir_bench);
-- 
1.9.1


From 06a05c6fbcb9d6866e3edb6f80441fd1f87e3daf Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Fri, 10 Oct 2014 14:32:19 -0700
Subject: [PATCH 16/22] s3: smbd: Leases - ensure all share mode removal
 functions go through a common lease refcount manager.

Signed-off-by: Jeremy Allison <jra at samba.org>
---
 source3/locking/locking.c | 136 ++++++++++++++++++++++++++++------------------
 1 file changed, 82 insertions(+), 54 deletions(-)

diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index 64bd043..7ccd8d4 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -618,6 +618,84 @@ bool is_valid_share_mode_entry(const struct share_mode_entry *e)
 }
 
 /*
+ * See if we need to remove a lease being referred to by a
+ * share mode that is being marked stale or deleted.
+ */
+
+static void remove_share_mode_lease(struct share_mode_data *d,
+				    struct share_mode_entry *e)
+{
+	struct GUID client_guid;
+	struct smb2_lease_key lease_key;
+	uint16_t op_type;
+	uint32_t lease_idx;
+	uint32_t i;
+
+	op_type = e->op_type;
+	e->op_type = NO_OPLOCK;
+
+	d->modified = true;
+
+	if (op_type != LEASE_OPLOCK) {
+		return;
+	}
+
+	/*
+	 * This used to reference a lease. If there's no other one referencing
+	 * it, remove it.
+	 */
+
+	lease_idx = e->lease_idx;
+	e->lease_idx = UINT32_MAX;
+
+	for (i=0; i<d->num_share_modes; i++) {
+		if (d->share_modes[i].stale) {
+			continue;
+		}
+		if (e == &d->share_modes[i]) {
+			/* Not ourselves. */
+			continue;
+		}
+		if (d->share_modes[i].lease_idx == lease_idx) {
+			break;
+		}
+	}
+	if (i < d->num_share_modes) {
+		/*
+		 * Found another one
+		 */
+		return;
+	}
+
+	memcpy(&client_guid,
+		&d->leases[lease_idx].client_guid,
+		sizeof(client_guid));
+	lease_key = d->leases[lease_idx].lease_key;
+
+	d->num_leases -= 1;
+	d->leases[lease_idx] = d->leases[d->num_leases];
+
+	/*
+	 * We changed the lease array. Fix all references to it.
+	 */
+	for (i=0; i<d->num_share_modes; i++) {
+		if (d->share_modes[i].lease_idx == d->num_leases) {
+			d->share_modes[i].lease_idx = lease_idx;
+		}
+	}
+
+	{
+		NTSTATUS status;
+
+		status = leases_db_del(&client_guid,
+					&lease_key);
+
+		DEBUG(10, ("%s: leases_db_del returned %s\n", __func__,
+			   nt_errstr(status)));
+	}
+}
+
+/*
  * In case d->share_modes[i] conflicts with something or otherwise is
  * being used, we need to make sure the corresponding process still
  * exists.
@@ -675,6 +753,8 @@ bool share_mode_stale_pid(struct share_mode_data *d, uint32_t idx)
 		}
 	}
 
+	remove_share_mode_lease(d, e);
+
 	d->modified = true;
 	return true;
 }
@@ -773,6 +853,7 @@ bool del_share_mode(struct share_mode_lock *lck, files_struct *fsp)
 	if (e == NULL) {
 		return False;
 	}
+	remove_share_mode_lease(lck->data, e);
 	*e = lck->data->share_modes[lck->data->num_share_modes-1];
 	lck->data->num_share_modes -= 1;
 	lck->data->modified = True;
@@ -822,67 +903,14 @@ bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp)
 {
 	struct share_mode_data *d = lck->data;
 	struct share_mode_entry *e;
-	uint16_t op_type;
-	uint32_t lease_idx;
-	uint32_t i;
 
 	e = find_share_mode_entry(lck, fsp);
 	if (e == NULL) {
 		return False;
 	}
 
-	op_type = e->op_type;
-	e->op_type = NO_OPLOCK;
-
+	remove_share_mode_lease(d, e);
 	d->modified = True;
-
-	if (op_type != LEASE_OPLOCK) {
-		return true;
-	}
-
-	/*
-	 * This used to reference a lease. If there's no other one referencing
-	 * it, remove it.
-	 */
-
-	lease_idx = e->lease_idx;
-	e->lease_idx = UINT32_MAX;
-
-	for (i=0; i<d->num_share_modes; i++) {
-		if (d->share_modes[i].lease_idx == lease_idx) {
-			break;
-		}
-	}
-	if (i < d->num_share_modes) {
-		/*
-		 * Found another one
-		 */
-		return true;
-	}
-
-	d->num_leases -= 1;
-	d->leases[lease_idx] = d->leases[d->num_leases];
-
-	/*
-	 * We changed the lease array. Fix all references to it.
-	 */
-	for (i=0; i<d->num_share_modes; i++) {
-		if (d->share_modes[i].lease_idx == d->num_leases) {
-			d->share_modes[i].lease_idx = lease_idx;
-		}
-	}
-
-	{
-		NTSTATUS status;
-
-		status = leases_db_del(
-			&fsp->conn->sconn->client->connections->smb2.client.guid,
-			&fsp->lease->lease.lease_key);
-
-		DEBUG(10, ("%s: leases_db_del returned %s\n", __func__,
-			   nt_errstr(status)));
-	}
-
 	return true;
 }
 
-- 
1.9.1


From 3c836c72c44a8886b2c2d8bd65500926d697ec27 Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Fri, 10 Oct 2014 16:36:54 -0700
Subject: [PATCH 17/22] s3: smbd: leases - expand the leases db to hold a list
 of file_ids (dev,ino) with this lease.

Will enable us to solve the dynamic share path problem.

Signed-off-by: Jeremy Allison <jra at samba.org>
---
 source3/librpc/idl/leases_db.idl |  24 +++
 source3/locking/leases_db.c      | 368 +++++++++++++++++++++++++++++++++++++++
 source3/locking/leases_db.h      |  46 +++++
 source3/locking/locking.c        |   3 +-
 4 files changed, 440 insertions(+), 1 deletion(-)
 create mode 100644 source3/librpc/idl/leases_db.idl
 create mode 100644 source3/locking/leases_db.c
 create mode 100644 source3/locking/leases_db.h

diff --git a/source3/librpc/idl/leases_db.idl b/source3/librpc/idl/leases_db.idl
new file mode 100644
index 0000000..28ec479
--- /dev/null
+++ b/source3/librpc/idl/leases_db.idl
@@ -0,0 +1,24 @@
+#include "idl_types.h"
+
+import "misc.idl";
+import "smb2_lease_struct.idl";
+import "file_id.idl";
+
+[
+	pointer_default(unique)
+]
+
+interface leases_db
+{
+	typedef [public] struct {
+	    GUID client_guid;
+	    smb2_lease_key lease_key;
+	} leases_db_key;
+
+	typedef [public] struct {
+		uint32 num_file_ids;
+		[size_is(num_file_ids)] file_id ids[];
+		[string,charset(UTF8)] char *filename;
+		[string,charset(UTF8)] char *stream_name;
+	} leases_db_value;
+}
diff --git a/source3/locking/leases_db.c b/source3/locking/leases_db.c
new file mode 100644
index 0000000..29b8e35
--- /dev/null
+++ b/source3/locking/leases_db.c
@@ -0,0 +1,368 @@
+/*
+   Unix SMB/CIFS implementation.
+   Map lease keys to file ids
+   Copyright (C) Volker Lendecke 2013
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "locking/leases_db.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "util_tdb.h"
+#include "ndr.h"
+#include "librpc/gen_ndr/ndr_leases_db.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/* the leases database handle */
+static struct db_context *leases_db;
+
+bool leases_db_init(bool read_only)
+{
+	if (leases_db) {
+		return true;
+	}
+
+	leases_db = db_open(NULL, lock_path("leases.tdb"), 0,
+			    TDB_DEFAULT|TDB_VOLATILE|TDB_CLEAR_IF_FIRST|
+			    TDB_INCOMPATIBLE_HASH,
+			    read_only ? O_RDONLY : O_RDWR|O_CREAT, 0644,
+			    DBWRAP_LOCK_ORDER_2, DBWRAP_FLAG_NONE);
+
+	if (leases_db == NULL) {
+		DEBUG(1, ("ERROR: Failed to initialise leases database\n"));
+		return false;
+	}
+
+	return true;
+}
+
+static bool leases_db_key(TALLOC_CTX *mem_ctx,
+			  const struct GUID *client_guid,
+			  const struct smb2_lease_key *lease_key,
+			  TDB_DATA *key)
+{
+	struct leases_db_key db_key = {
+		.client_guid = *client_guid,
+		.lease_key = *lease_key };
+	DATA_BLOB blob;
+	enum ndr_err_code ndr_err;
+
+	if (DEBUGLEVEL >= 10) {
+		DEBUG(10, ("%s:\n", __func__));
+		NDR_PRINT_DEBUG(leases_db_key, &db_key);
+	}
+
+	ndr_err = ndr_push_struct_blob(
+		&blob, mem_ctx, &db_key,
+		(ndr_push_flags_fn_t)ndr_push_leases_db_key);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		DEBUG(10, ("%s: ndr_push_struct_blob_failed: %s\n",
+			   __func__, ndr_errstr(ndr_err)));
+		return false;
+	}
+
+	*key = make_tdb_data(blob.data, blob.length);
+	return true;
+}
+
+NTSTATUS leases_db_add(const struct GUID *client_guid,
+		       const struct smb2_lease_key *lease_key,
+		       const struct file_id *id,
+		       const char *filename,
+		       const char *stream_name)
+{
+	TDB_DATA db_key, db_value;
+	DATA_BLOB blob;
+	struct db_record *rec;
+	NTSTATUS status;
+	bool ok;
+	struct leases_db_value new_value;
+	struct leases_db_value *value = NULL;
+	enum ndr_err_code ndr_err;
+
+	if (!leases_db_init(false)) {
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	ok = leases_db_key(talloc_tos(), client_guid, lease_key, &db_key);
+	if (!ok) {
+		DEBUG(10, ("%s: leases_db_key failed\n", __func__));
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	rec = dbwrap_fetch_locked(leases_db, talloc_tos(), db_key);
+	TALLOC_FREE(db_key.dptr);
+	if (rec == NULL) {
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	db_value = dbwrap_record_get_value(rec);
+	if (db_value.dsize != 0) {
+		uint32_t i;
+
+		DEBUG(10, ("%s: record exists\n", __func__));
+
+		value = talloc(talloc_tos(), struct leases_db_value);
+		if (value == NULL) {
+			status = NT_STATUS_NO_MEMORY;
+			goto out;
+		}
+
+		blob.data = db_value.dptr;
+		blob.length = db_value.dsize;
+
+		ndr_err = ndr_pull_struct_blob_all(
+			&blob, value, value,
+			(ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
+		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+			DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n",
+				   __func__, ndr_errstr(ndr_err)));
+			status = ndr_map_error2ntstatus(ndr_err);
+			goto out;
+		}
+
+		/* id must be unique. */
+		for (i = 0; i < value->num_file_ids; i++) {
+			if (file_id_equal(id, &value->ids[i])) {
+				status = NT_STATUS_OBJECT_NAME_COLLISION;
+				goto out;
+			}
+		}
+
+		value->ids = talloc_realloc(value, value->ids, struct file_id,
+					value->num_file_ids + 1);
+		if (value->ids == NULL) {
+			status = NT_STATUS_NO_MEMORY;
+			goto out;
+		}
+		value->ids[value->num_file_ids] = *id;
+		value->num_file_ids += 1;
+
+	} else {
+		new_value = (struct leases_db_value) {
+			.num_file_ids = 1,
+			.ids = id,
+			.filename = filename,
+			.stream_name = stream_name,
+		};
+		value = &new_value;
+	}
+
+	ndr_err = ndr_push_struct_blob(
+		&blob, talloc_tos(), value,
+		(ndr_push_flags_fn_t)ndr_push_leases_db_value);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		DEBUG(10, ("%s: ndr_push_struct_blob_failed: %s\n",
+			   __func__, ndr_errstr(ndr_err)));
+		status = ndr_map_error2ntstatus(ndr_err);
+		goto out;
+	}
+
+	db_value = make_tdb_data(blob.data, blob.length);
+
+	status = dbwrap_record_store(rec, db_value, 0);
+	if (!NT_STATUS_IS_OK(status)) {
+		DEBUG(10, ("%s: dbwrap_record_store returned %s\n",
+			   __func__, nt_errstr(status)));
+	}
+
+  out:
+
+	if (value != &new_value) {
+		TALLOC_FREE(value);
+	}
+	TALLOC_FREE(rec);
+	return status;
+}
+
+NTSTATUS leases_db_del(const struct GUID *client_guid,
+		       const struct smb2_lease_key *lease_key,
+		       const struct file_id *id)
+{
+	TDB_DATA db_key, db_value;
+	struct db_record *rec;
+	NTSTATUS status;
+	struct leases_db_value *value;
+	enum ndr_err_code ndr_err;
+	DATA_BLOB blob;
+	uint32_t i;
+	bool ok;
+
+	if (!leases_db_init(false)) {
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	ok = leases_db_key(talloc_tos(), client_guid, lease_key, &db_key);
+	if (!ok) {
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	rec = dbwrap_fetch_locked(leases_db, talloc_tos(), db_key);
+	TALLOC_FREE(db_key.dptr);
+	if (rec == NULL) {
+		return NT_STATUS_NOT_FOUND;
+	}
+	db_value = dbwrap_record_get_value(rec);
+	if (db_value.dsize == 0) {
+		status = NT_STATUS_INTERNAL_ERROR;
+		goto out;
+	}
+
+	value = talloc(talloc_tos(), struct leases_db_value);
+	if (value == NULL) {
+		status = NT_STATUS_NO_MEMORY;
+		goto out;
+	}
+
+	blob.data = db_value.dptr;
+	blob.length = db_value.dsize;
+
+	ndr_err = ndr_pull_struct_blob_all(
+		&blob, value, value,
+		(ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n",
+			   __func__, ndr_errstr(ndr_err)));
+		status = ndr_map_error2ntstatus(ndr_err);
+		goto out;
+	}
+
+	/* id must exist. */
+	for (i = 0; i < value->num_file_ids; i++) {
+		if (file_id_equal(id, &value->ids[i])) {
+			break;
+		}
+	}
+
+	if (i == value->num_file_ids) {
+		status = NT_STATUS_NOT_FOUND;
+		goto out;
+	}
+
+	value->ids[i] = value->ids[value->num_file_ids-1];
+	value->num_file_ids -= 1;
+
+	if (value->num_file_ids == 0) {
+		status = dbwrap_record_delete(rec);
+	} else {
+		ndr_err = ndr_push_struct_blob(
+			&blob, talloc_tos(), value,
+			(ndr_push_flags_fn_t)ndr_push_leases_db_value);
+		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+			DEBUG(10, ("%s: ndr_push_struct_blob_failed: %s\n",
+				   __func__, ndr_errstr(ndr_err)));
+			status = ndr_map_error2ntstatus(ndr_err);
+			goto out;
+		}
+
+		db_value = make_tdb_data(blob.data, blob.length);
+
+		status = dbwrap_record_store(rec, db_value, 0);
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(10, ("%s: dbwrap_record_store returned %s\n",
+				   __func__, nt_errstr(status)));
+		}
+	}
+
+  out:
+
+	TALLOC_FREE(value);
+	TALLOC_FREE(rec);
+	return status;
+}
+
+struct leases_db_fetch_state {
+	void (*parser)(uint32_t num_file_ids,
+			struct file_id *ids, const char *filename,
+			const char *stream_name, void *private_data);
+	void *private_data;
+	NTSTATUS status;
+};
+
+static void leases_db_parser(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+	struct leases_db_fetch_state *state =
+		(struct leases_db_fetch_state *)private_data;
+	DATA_BLOB blob = { .data = data.dptr, .length = data.dsize };
+	enum ndr_err_code ndr_err;
+	struct leases_db_value *value;
+
+	value = talloc(talloc_tos(), struct leases_db_value);
+	if (value == NULL) {
+		state->status = NT_STATUS_NO_MEMORY;
+		return;
+	}
+
+	ndr_err = ndr_pull_struct_blob_all(
+		&blob, value, value,
+		(ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n",
+			   __func__, ndr_errstr(ndr_err)));
+		TALLOC_FREE(value);
+		state->status = ndr_map_error2ntstatus(ndr_err);
+		return;
+	}
+
+	state->parser(value->num_file_ids,
+			value->ids, value->filename, value->stream_name,
+			state->private_data);
+
+	TALLOC_FREE(value);
+	state->status = NT_STATUS_OK;
+}
+
+NTSTATUS leases_db_parse(const struct GUID *client_guid,
+			 const struct smb2_lease_key *lease_key,
+			 void (*parser)(uint32_t num_file_ids,
+					struct file_id *ids,
+					const char *filename,
+					const char *stream_name,
+					void *private_data),
+			 void *private_data)
+{
+	TDB_DATA db_key;
+	struct leases_db_fetch_state state;
+	NTSTATUS status;
+	bool ok;
+
+	if (!leases_db_init(true)) {
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	ok = leases_db_key(talloc_tos(), client_guid, lease_key, &db_key);
+	if (!ok) {
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	state = (struct leases_db_fetch_state) {
+		.parser = parser,
+		.private_data = private_data,
+		.status = NT_STATUS_OK
+	};
+
+	status = dbwrap_parse_record(leases_db, db_key, leases_db_parser,
+				     &state);
+	TALLOC_FREE(db_key.dptr);
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
+	return state.status;
+}
diff --git a/source3/locking/leases_db.h b/source3/locking/leases_db.h
new file mode 100644
index 0000000..f570356
--- /dev/null
+++ b/source3/locking/leases_db.h
@@ -0,0 +1,46 @@
+/*
+ *  Unix SMB/CIFS implementation.
+ *  leases.tdb functions
+ *
+ *  Copyright (C) Volker Lendecke 2014
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LEASES_DB_H_
+#define _LEASES_DB_H_
+
+struct GUID;
+struct smb2_lease_key;
+struct file_id;
+
+bool leases_db_init(bool read_only);
+NTSTATUS leases_db_add(const struct GUID *client_guid,
+		       const struct smb2_lease_key *lease_key,
+		       const struct file_id *id,
+		       const char *filename,
+		       const char *stream_name);
+NTSTATUS leases_db_del(const struct GUID *client_guid,
+		       const struct smb2_lease_key *lease_key,
+		       const struct file_id *id);
+NTSTATUS leases_db_parse(const struct GUID *client_guid,
+			 const struct smb2_lease_key *lease_key,
+			 void (*parser)(uint32_t num_file_ids,
+					struct file_id *ids,
+					const char *filename,
+					const char *stream_name,
+					void *private_data),
+			 void *private_data);
+
+#endif /* _LEASES_DB_H_ */
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index 7ccd8d4..1c6707e 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -688,7 +688,8 @@ static void remove_share_mode_lease(struct share_mode_data *d,
 		NTSTATUS status;
 
 		status = leases_db_del(&client_guid,
-					&lease_key);
+					&lease_key,
+					&e->id);
 
 		DEBUG(10, ("%s: leases_db_del returned %s\n", __func__,
 			   nt_errstr(status)));
-- 
1.9.1


From 3982a24e4a0ebe3c32d81d9d7b7a6a5175c6a65a Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Fri, 10 Oct 2014 17:05:46 -0700
Subject: [PATCH 18/22] s3: smbd: leases - Fix the dynamic share file case
 [homes].

Signed-off-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/open.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 135 insertions(+), 16 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 869869a..533347f 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -4084,39 +4084,148 @@ static NTSTATUS inherit_new_acl(files_struct *fsp)
  */
 
 struct lease_fname_match_state {
+	/* Input parameters. */
 	const struct smb_filename *fname;
-	bool match;
+	bool file_existed;
+	struct file_id id;
+	/* Return parameters. */
+	uint32_t num_file_ids;
+	struct file_id *ids;
+	NTSTATUS match_status;
 };
 
 static void lease_fname_match_parser(
-	struct file_id id, const char *filename, const char *stream_name,
+	uint32_t num_file_ids,
+	struct file_id *ids, const char *filename, const char *stream_name,
 	void *private_data)
 {
 	struct lease_fname_match_state *state =
 		(struct lease_fname_match_state *)private_data;
 
-	state->match =
-		strequal(filename, state->fname->base_name) &&
-		strequal(stream_name, state->fname->stream_name);
+	if (!strequal(filename, state->fname->base_name) ||
+			!strequal(stream_name, state->fname->stream_name)) {
+		/* Names don't match lease key. */
+		state->match_status = NT_STATUS_INVALID_PARAMETER;
+		return;
+	}
+
+	if (!state->file_existed) {
+		/* New file. */
+		state->match_status = NT_STATUS_OK;
+		return;
+	}
+
+	if (num_file_ids == 1 && file_id_equal(&ids[0],&state->id)) {
+		/* Common case - non-dynamic share. We're ok.. */
+		state->match_status = NT_STATUS_OK;
+		return;
+	}
+
+	/* More than one file id, or not equal. Don't allow leases. */
+	state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+	state->num_file_ids = num_file_ids;
+	state->ids = talloc_memdup(talloc_tos(),
+				ids,
+				num_file_ids * sizeof(struct file_id));
+	if (state->ids == NULL) {
+		state->match_status = NT_STATUS_NO_MEMORY;
+	}
 }
 
-static bool lease_fname_match(struct smbd_server_connection *sconn,
-			      struct smb2_lease_key *lease_key,
-			      const struct smb_filename *fname)
+static NTSTATUS lease_match(connection_struct *conn,
+			    struct smb_request *req,
+			    struct smb2_lease_key *lease_key,
+			    const struct smb_filename *fname)
 {
-	struct lease_fname_match_state state =
-		{ .fname = fname, .match = true };
+	struct smbd_server_connection *sconn = req->sconn;
+	struct lease_fname_match_state state = {
+		.fname = fname,
+		.match_status = NT_STATUS_OK
+	};
+	uint32_t i;
 	NTSTATUS status;
 
+	state.file_existed = VALID_STAT(fname->st);
+	if (state.file_existed) {
+		state.id = vfs_file_id_from_sbuf(conn, &fname->st);
+	}
+
 	status = leases_db_parse(&sconn->client->connections->smb2.client.guid,
 				 lease_key, lease_fname_match_parser, &state);
 	if (!NT_STATUS_IS_OK(status)) {
 		/*
 		 * Not found or error means okay: We can make the lease pass
 		 */
-		return true;
+		return NT_STATUS_OK;
 	}
-	return state.match;
+	if (!NT_STATUS_EQUAL(state.match_status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+		/*
+		 * Anything but NT_STATUS_OPLOCK_NOT_GRANTED, let the caller
+		 * deal with it.
+		 */
+		return state.match_status;
+	}
+
+	/* We have to break all existing leases. */
+	for (i = 0; i < state.num_file_ids; i++) {
+		struct share_mode_lock *lck;
+		struct share_mode_data *d;
+		uint32_t j;
+
+		if (file_id_equal(&state.ids[i], &state.id)) {
+			/* Don't need to break our own file. */
+			continue;
+		}
+		lck = get_existing_share_mode_lock(talloc_tos(), state.ids[i]);
+		if (lck == NULL) {
+			/* Race condition - file already closed. */
+			continue;
+		}
+		d = lck->data;
+		for (j=0; j<d->num_share_modes; j++) {
+			struct share_mode_entry *e = &d->share_modes[j];
+			uint32_t e_lease_type = get_lease_type(d, e);
+
+			if (e_lease_type == SMB2_LEASE_NONE) {
+				continue;
+			}
+			if (share_mode_stale_pid(d, j)) {
+				continue;
+			}
+
+			send_break_message(conn->sconn->msg_ctx, e,
+					SMB2_LEASE_NONE);
+
+			/*
+			 * Windows 7 and 8 lease clients
+			 * are broken in that they will not
+			 * respond to lease break requests
+			 * whilst waiting for an outstanding
+			 * open request on that lease handle
+			 * on the same TCP connection, due
+			 * to holding an internal inode lock.
+			 *
+			 * This means we can't reschedule
+			 * ourselves here, but must return
+			 * from the create.
+			 *
+			 * Work around:
+			 *
+			 * Send the breaks and then return
+			 * SMB2_LEASE_NONE in the lease handle
+			 * to cause them to acknowledge the
+			 * lease break. Consulatation with
+			 * Microsoft engineering confirmed
+			 * this approach is safe.
+			 */
+		}
+		TALLOC_FREE(lck);
+	}
+	/*
+	 * Ensure we don't grant anything more so we
+	 * never upgrade.
+	 */
+	return NT_STATUS_OPLOCK_NOT_GRANTED;
 }
 
 /*
@@ -4175,10 +4284,20 @@ static NTSTATUS create_file_unixpath(connection_struct *conn,
 		oplock_request |= INTERNAL_OPEN_ONLY;
 	}
 
-	if ((lease != NULL) &&
-	    !lease_fname_match(req->sconn, &lease->lease_key, smb_fname)) {
-		status = NT_STATUS_INVALID_PARAMETER;
-		goto fail;
+	if (lease != NULL) {
+		status = lease_match(conn,
+				req,
+				&lease->lease_key,
+				smb_fname);
+		if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+			/* Dynamic share file. No leases and update epoch... */
+			lease->lease_state = SMB2_LEASE_NONE;
+			if (lease->lease_version > 1) {
+				lease->lease_epoch += 1;
+			}
+		} else if (!NT_STATUS_IS_OK(status)) {
+			goto fail;
+		}
 	}
 
 	if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
-- 
1.9.1


From 317a966c15939029d1dfe327ed7418e6dc8b8c3f Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Tue, 14 Oct 2014 10:24:31 -0700
Subject: [PATCH 19/22] s3: smbd: leases - If find_fsp_lease() doesn't find a
 lease this isn't an error.

As the lease exists in the oplock db, then it just must be leased in
another smbd (multi-connection case).

This unifies code that previously existed only inside the durable
reconnect case.

Signed-off-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/durable.c | 21 +++++----------------
 source3/smbd/open.c    | 24 ++++++++++++++++--------
 source3/smbd/proto.h   |  4 +++-
 3 files changed, 24 insertions(+), 25 deletions(-)

diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
index 660865d..73bcd58 100644
--- a/source3/smbd/durable.c
+++ b/source3/smbd/durable.c
@@ -731,22 +731,11 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
 		key.data[0] = o->lease_key.data[0];
 		key.data[1] = o->lease_key.data[1];
 
-		fsp->lease = find_fsp_lease(fsp, &key);
-
-		if (fsp->lease != NULL) {
-			fsp->lease->ref_count += 1;
-		} else {
-			fsp->lease = talloc_zero(fsp->conn->sconn,
-						 struct fsp_lease);
-			if (fsp->lease == NULL) {
-				TALLOC_FREE(lck);
-				fsp_free(fsp);
-				return NT_STATUS_NO_MEMORY;
-			}
-			fsp->lease->ref_count = 1;
-			fsp->lease->lease.lease_key = key;
-			fsp->lease->lease.lease_state = o->current_state;
-			fsp->lease->lease.lease_epoch = o->epoch;
+		fsp->lease = find_fsp_lease(fsp, &key, o);
+		if (fsp->lease == NULL) {
+			TALLOC_FREE(lck);
+			fsp_free(fsp);
+			return NT_STATUS_NO_MEMORY;
 		}
 	}
 
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 533347f..7097c84 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1552,7 +1552,8 @@ static bool is_same_lease(const struct share_mode_data *d,
 }
 
 struct fsp_lease *find_fsp_lease(files_struct *new_fsp,
-				 const struct smb2_lease_key *key)
+				 const struct smb2_lease_key *key,
+				 const struct share_mode_oplock *o)
 {
 	files_struct *fsp;
 
@@ -1572,11 +1573,21 @@ struct fsp_lease *find_fsp_lease(files_struct *new_fsp,
 			continue;
 		}
 		if (smb2_lease_key_equal(&fsp->lease->lease.lease_key, key)) {
+			fsp->lease->ref_count += 1;
 			return fsp->lease;
 		}
 	}
 
-	return NULL;
+	/* Not found - must be leased in another smbd. */
+	new_fsp->lease = talloc_zero(new_fsp->conn->sconn, struct fsp_lease);
+	if (new_fsp->lease == NULL) {
+		return NULL;
+	}
+	new_fsp->lease->ref_count = 1;
+	new_fsp->lease->lease.lease_key = *key;
+	new_fsp->lease->lease.lease_state = o->current_state;
+	new_fsp->lease->lease.lease_epoch = o->epoch;
+	return new_fsp->lease;
 }
 
 static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d,
@@ -1585,7 +1596,6 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d,
 				uint32_t granted)
 {
 	const struct GUID *client_guid;
-	struct share_mode_oplock *o;
 	struct share_mode_oplock *tmp;
 	NTSTATUS status;
 	int idx;
@@ -1599,20 +1609,18 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d,
 	idx = find_share_mode_oplock(d, client_guid, &lease->lease_key);
 
 	if (idx != -1) {
-
+		struct share_mode_oplock *o = &d->leases[idx];
 		bool do_upgrade;
 		uint32_t existing, requested;
 
-		fsp->lease = find_fsp_lease(fsp, &lease->lease_key);
+		fsp->lease = find_fsp_lease(fsp, &lease->lease_key, o);
 		if (fsp->lease == NULL) {
 			DEBUG(1, ("Did not find existing lease for file %s\n",
 				  fsp_str_dbg(fsp)));
-			return NT_STATUS_INTERNAL_ERROR;
+			return NT_STATUS_NO_MEMORY;
 		}
-		fsp->lease->ref_count += 1;
 
 		*p_lease_idx = idx;
-		o = &d->leases[idx];
 
 		/*
 		 * Upgrade only if the requested lease is a strict upgrade.
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 08ad413..22b1a82 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -613,8 +613,10 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
 				    const char *inherit_from_dir,
 				    const char *fname,
 				    SMB_STRUCT_STAT *psbuf);
+struct share_mode_oplock;
 struct fsp_lease *find_fsp_lease(files_struct *new_fsp,
-				 const struct smb2_lease_key *key);
+				 const struct smb2_lease_key *key,
+				 const struct share_mode_oplock *o);
 bool is_stat_open(uint32 access_mask);
 struct deferred_open_record;
 bool is_deferred_open_async(const struct deferred_open_record *rec);
-- 
1.9.1


From e474a4edc4753cfdb4471e05129b4ee3b7c56ebe Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Wed, 22 Oct 2014 17:53:01 -0700
Subject: [PATCH 20/22] s3: leases: Don't set fsp->oplock_type before we've
 granted any oplocks.

It's not needed, and when we have leases it causes a crash in the call
stack when truncate is requested as follows:

open_file_ntcreate()->vfs_set_filelen()->smbd_contend_level2_oplocks_begin()->
	contend_level2_oplocks_begin_default().

Inside which we check if fsp->oplock_type == LEASE_TYPE.

In this case we have not yet added the entry in the share
mode database so fsp->lease == NULL.

Signed-off-by: Jeremy Allison <jra at samba.org>
---
 source3/smbd/open.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 7097c84..dfa3cf4 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -2688,9 +2688,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 					      * the open is done. */
 	fsp->posix_open = posix_open;
 
-	/* Ensure no SAMBA_PRIVATE bits can be set. */
-	fsp->oplock_type = (oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
-
 	if (timeval_is_zero(&request_time)) {
 		request_time = fsp->open_time;
 	}
-- 
1.9.1


From b737507c33d619e83aff6d8621516aab99eabea6 Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Tue, 14 Oct 2014 10:34:53 -0700
Subject: [PATCH 21/22] s3: smbd: Add "smb2 leases" parameter. Default "false".

Signed-off-by: Jeremy Allison <jra at samba.org>
---
 docs-xml/smbdotconf/locking/smb2leases.xml | 19 +++++++++++++++++++
 lib/param/param_table.c                    |  9 +++++++++
 selftest/target/Samba3.pm                  |  1 +
 source3/param/loadparm.c                   |  1 +
 source3/smbd/smb2_negprot.c                |  2 +-
 5 files changed, 31 insertions(+), 1 deletion(-)
 create mode 100644 docs-xml/smbdotconf/locking/smb2leases.xml

diff --git a/docs-xml/smbdotconf/locking/smb2leases.xml b/docs-xml/smbdotconf/locking/smb2leases.xml
new file mode 100644
index 0000000..2927e56
--- /dev/null
+++ b/docs-xml/smbdotconf/locking/smb2leases.xml
@@ -0,0 +1,19 @@
+<samba:parameter name="smb2 leases"
+                 context="G"
+				 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+	<para>
+	This boolean option tells <command moreinfo="none">smbd</command> whether to
+	globally negotiate SMB2 leases on file open requests. Leasing is an SMB2-only
+	feature which allows clients to aggressively cache files locally above and
+	beyond the caching allowed by SMB1 oplocks. This (experimental) parameter is
+	set to off by default until the SMB2 leasing code is declared fully stable.
+	</para>
+</description>
+
+<related>oplocks</related>
+<related>kernel oplocks</related>
+<related>level2 oplocks</related>
+<value type="default">no</value>
+</samba:parameter>
diff --git a/lib/param/param_table.c b/lib/param/param_table.c
index 15ffa8c..f68d2cc 100644
--- a/lib/param/param_table.c
+++ b/lib/param/param_table.c
@@ -3001,6 +3001,15 @@ struct parm_struct parm_table[] = {
 		.flags		= FLAG_ADVANCED | FLAG_SHARE | FLAG_GLOBAL,
 	},
 	{
+		.label		= "smb2 leases",
+		.type		= P_BOOL,
+		.p_class	= P_GLOBAL,
+		.offset		= GLOBAL_VAR(smb2_leases),
+		.special	= NULL,
+		.enum_list	= NULL,
+		.flags		= FLAG_ADVANCED | FLAG_SHARE,
+	},
+	{
 		.label		= "locking",
 		.type		= P_BOOL,
 		.p_class	= P_LOCAL,
diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index de40ced..426c787 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1048,6 +1048,7 @@ sub provision($$$$$$)
 
 	kernel oplocks = no
 	kernel change notify = no
+	smb2 leases = yes
 
 	syslog = no
 	printing = bsd
diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c
index 52ffbcc..8c388f3 100644
--- a/source3/param/loadparm.c
+++ b/source3/param/loadparm.c
@@ -853,6 +853,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals)
 	Globals.smb2_max_write = DEFAULT_SMB2_MAX_WRITE;
 	Globals.smb2_max_trans = DEFAULT_SMB2_MAX_TRANSACT;
 	Globals.ismb2_max_credits = DEFAULT_SMB2_MAX_CREDITS;
+	Globals.smb2_leases = false;
 
 	string_set(Globals.ctx, &Globals.ncalrpc_dir, get_dyn_NCALRPCDIR());
 
diff --git a/source3/smbd/smb2_negprot.c b/source3/smbd/smb2_negprot.c
index a27ab0c..e88165b 100644
--- a/source3/smbd/smb2_negprot.c
+++ b/source3/smbd/smb2_negprot.c
@@ -230,7 +230,7 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req)
 		capabilities |= SMB2_CAP_DFS;
 	}
 
-	if (protocol >= PROTOCOL_SMB2_10) {
+	if (protocol >= PROTOCOL_SMB2_10 && lp_smb2_leases()) {
 		capabilities |= SMB2_CAP_LEASING;
 	}
 
-- 
1.9.1


From c8d006719499caa0418ceba2bdece9cf46a880a3 Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Fri, 24 Oct 2014 13:57:04 -0700
Subject: [PATCH 22/22] s3: smbd: Add new option "strict rename".

Control whether smbd can rename directories containing
open files. Defaults to "no" (meaning we *can* do
such renames). autobuild test set with it to "yes".

Only used if lp_posix_pathnames() == false.

Signed-off-by: Jeremy Allison <jra at samba.org>
---
 docs-xml/smbdotconf/tuning/strictrename.xml | 25 +++++++++++++++++++++++++
 lib/param/param_table.c                     |  9 +++++++++
 selftest/target/Samba3.pm                   |  1 +
 source3/param/loadparm.c                    |  1 +
 source3/smbd/dir.c                          |  4 +++-
 5 files changed, 39 insertions(+), 1 deletion(-)
 create mode 100644 docs-xml/smbdotconf/tuning/strictrename.xml

diff --git a/docs-xml/smbdotconf/tuning/strictrename.xml b/docs-xml/smbdotconf/tuning/strictrename.xml
new file mode 100644
index 0000000..5478863
--- /dev/null
+++ b/docs-xml/smbdotconf/tuning/strictrename.xml
@@ -0,0 +1,25 @@
+<samba:parameter name="strict rename"
+                 context="S"
+				 type="boolean"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+    <para>By default a Windows SMB server prevents directory
+    renames when there are open file or directory handles below
+    it in the filesystem hierarchy. Historically Samba has always
+    allowed this as POSIX filesystem semantics require it.</para>
+
+    <para>This boolean parameter allows Samba to match the Windows
+    behavior. Setting this to "yes" is a very expensive change,
+    as it forces Samba to travers the entire open file handle
+    database on every directory rename request. In a clustered
+    Samba system the cost is even greater than the non-clustered
+    case.</para>
+
+    <para>For this reason the default is "no", and it is recommended
+    to be left that way unless a specific Windows application requires
+    it to be changed.</para>
+
+</description>
+
+<value type="default">no</value>
+</samba:parameter>
diff --git a/lib/param/param_table.c b/lib/param/param_table.c
index f68d2cc..b6ff571 100644
--- a/lib/param/param_table.c
+++ b/lib/param/param_table.c
@@ -1884,6 +1884,15 @@ struct parm_struct parm_table[] = {
 		.flags		= FLAG_ADVANCED | FLAG_SHARE,
 	},
 	{
+		.label		= "strict rename",
+		.type		= P_BOOL,
+		.p_class	= P_LOCAL,
+		.offset		= LOCAL_VAR(strict_rename),
+		.special	= NULL,
+		.enum_list	= NULL,
+		.flags		= FLAG_ADVANCED | FLAG_SHARE,
+	},
+	{
 		.label		= "strict sync",
 		.type		= P_BOOL,
 		.p_class	= P_LOCAL,
diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index 426c787..f329cfb 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1074,6 +1074,7 @@ sub provision($$$$$$)
 	store dos attributes = yes
 	create mask = 755
 	dos filemode = yes
+	strict rename = yes
 	vfs objects = acl_xattr fake_acls xattr_tdb streams_depot
 
 	printing = vlp
diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c
index 8c388f3..a0f3eef 100644
--- a/source3/param/loadparm.c
+++ b/source3/param/loadparm.c
@@ -206,6 +206,7 @@ static struct loadparm_service sDefault =
 	.follow_symlinks = true,
 	.sync_always = false,
 	.strict_allocate = false,
+	.strict_rename = false,
 	.strict_sync = false,
 	.mangling_char = '~',
 	.copymap = NULL,
diff --git a/source3/smbd/dir.c b/source3/smbd/dir.c
index 29184a3..3fd1ba9 100644
--- a/source3/smbd/dir.c
+++ b/source3/smbd/dir.c
@@ -1966,7 +1966,9 @@ NTSTATUS can_delete_directory_fsp(files_struct *fsp)
 		return status;
 	}
 
-	if (have_file_open_below(fsp->conn, fsp->fsp_name)) {
+	if (!lp_posix_pathnames() &&
+			lp_strict_rename(SNUM(conn)) &&
+			have_file_open_below(fsp->conn, fsp->fsp_name)) {
 		return NT_STATUS_ACCESS_DENIED;
 	}
 
-- 
1.9.1



More information about the samba-technical mailing list