Current SMB2 leases patchset (WIP).

Jeremy Allison jra at samba.org
Fri Oct 24 18:20:25 MDT 2014


Here is a snapshot of Volker's leases
code that applies onto master with a
few fixes and additional changes created
by me. It's currently being tested at
a couple of vendors, but I wanted to
make it more widely available (yes
I know I should upload it to a branch
on git.samba.org :-) for people to
look at and test.

If you apply it, you'll need to
add:

smb2 leases = yes

in the [global] section of your
smb.conf (it's set to off be
default).

I think it's getting close to official
submission for master (and hopefully
then into 4.2.0).

Enjoy !

Cheers,

	Jeremy.
-------------- next part --------------
From 81954bef097990817479ecc5c29792400891b70d 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/21] 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:
-- 
2.1.0.rc2.206.gedb03e5


From 34f067bc9455f0b53191b9eced4e297ebda3ff89 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Mon, 7 Jul 2014 11:51:04 +0000
Subject: [PATCH 02/21] 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/leases_db.idl    |  23 ++
 source3/librpc/idl/open_files.idl   |  30 ++
 source3/librpc/idl/wscript_build    |   1 +
 source3/librpc/wscript_build        |   7 +-
 source3/locking/brlock.c            | 125 ++++----
 source3/locking/leases_db.c         | 251 ++++++++++++++++
 source3/locking/leases_db.h         |  44 +++
 source3/locking/locking.c           | 140 ++++++++-
 source3/locking/proto.h             |  17 +-
 source3/locking/share_mode_lock.c   |   4 +-
 source3/smbd/durable.c              |  28 +-
 source3/smbd/files.c                |  34 +++
 source3/smbd/globals.h              |  10 +-
 source3/smbd/open.c                 | 577 +++++++++++++++++++++++++++---------
 source3/smbd/oplock.c               | 362 ++++++++++++++++------
 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 +-
 29 files changed, 1757 insertions(+), 328 deletions(-)
 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/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/leases_db.idl b/source3/librpc/idl/leases_db.idl
new file mode 100644
index 0000000..9ec8912
--- /dev/null
+++ b/source3/librpc/idl/leases_db.idl
@@ -0,0 +1,23 @@
+#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 {
+		file_id id;
+		[string,charset(UTF8)] char *filename;
+		[string,charset(UTF8)] char *stream_name;
+	} leases_db_value;
+}
diff --git a/source3/librpc/idl/open_files.idl b/source3/librpc/idl/open_files.idl
index 4278301..6604ff3 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;
+		uint16		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/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;
 
diff --git a/source3/locking/leases_db.c b/source3/locking/leases_db.c
new file mode 100644
index 0000000..69b1362
--- /dev/null
+++ b/source3/locking/leases_db.c
@@ -0,0 +1,251 @@
+/*
+   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;
+	struct db_record *rec;
+	NTSTATUS status;
+	bool ok;
+	struct leases_db_value value;
+	DATA_BLOB blob;
+	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) {
+		DEBUG(10, ("%s: record exists\n", __func__));
+		TALLOC_FREE(rec);
+		return NT_STATUS_OBJECT_NAME_COLLISION;
+	}
+
+	value = (struct leases_db_value) {
+		.id = *id,
+		.filename = filename,
+		.stream_name = stream_name,
+	};
+
+	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)));
+		TALLOC_FREE(rec);
+		return ndr_map_error2ntstatus(ndr_err);
+	}
+
+	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)));
+	}
+
+	TALLOC_FREE(rec);
+	return status;
+}
+
+NTSTATUS leases_db_del(const struct GUID *client_guid,
+		       const struct smb2_lease_key *lease_key)
+{
+	TDB_DATA db_key;
+	struct db_record *rec;
+	NTSTATUS status;
+	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;
+	}
+	status = dbwrap_record_delete(rec);
+	TALLOC_FREE(rec);
+	return status;
+}
+
+struct leases_db_fetch_state {
+	void (*parser)(struct file_id id, 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->id, 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)(struct file_id id,
+					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..ff9c362
--- /dev/null
+++ b/source3/locking/leases_db.h
@@ -0,0 +1,44 @@
+/*
+ *  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);
+NTSTATUS leases_db_parse(const struct GUID *client_guid,
+			 const struct smb2_lease_key *lease_key,
+			 void (*parser)(struct file_id id,
+					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 a320068..cf1b497 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,
+		    uint16_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,69 @@ 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, 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 = UINT16_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 +903,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..f2cf724 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,
+		    uint16_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/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"));
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..7eda52b 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,
+				uint16_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;
+	uint16_t lease_idx = UINT16_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, UINT16_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..eae5f0f 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,6 +781,19 @@ 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)
@@ -647,6 +802,7 @@ static void do_break_to_none(struct tevent_context *ctx,
 		private_data, struct break_to_none_state);
 	int 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);			\
-- 
2.1.0.rc2.206.gedb03e5


From 696f58b72387bf1e8e269cfebd58bc0897b22696 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 03/21] 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)
-- 
2.1.0.rc2.206.gedb03e5


From 15dbd4f9cb3463c8fccd6d1d47387a265f854dcb 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 04/21] 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
-- 
2.1.0.rc2.206.gedb03e5


From a84f84092cdd19bf22179b17e6115ae8380d3ff3 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 05/21] 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 7eda52b..7d5be7b 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 eae5f0f..eac7636 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)
-- 
2.1.0.rc2.206.gedb03e5


From b8dbe317f88b3609a1d32456d4455a74b5fdf43c 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 06/21] 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 f2cf724..49e74df 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");
-- 
2.1.0.rc2.206.gedb03e5


From 11721830c910234dca6de2e0121c2bef59d3bd91 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 07/21] 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 49e74df..ad17ed0 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,
-- 
2.1.0.rc2.206.gedb03e5


From 7fb36f606d63d35abb2c3a3caa115800ee363a3e 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 08/21] 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);
-- 
2.1.0.rc2.206.gedb03e5


From 95b9664b14ed0aa33b0637ead8579b664968486d 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 09/21] 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 7d5be7b..4886f3d 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 eac7636..0026bd5 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;
-- 
2.1.0.rc2.206.gedb03e5


From a38ab41b608dc3508b2a712909b9a713bc7a0780 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 10/21] 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 4886f3d..a5a8dd0 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:
-- 
2.1.0.rc2.206.gedb03e5


From cdc652fb34b1e07f186e9536146abade6d302e79 Mon Sep 17 00:00:00 2001
From: Volker Lendecke <vl at samba.org>
Date: Wed, 24 Sep 2014 03:30:35 +0200
Subject: [PATCH 11/21] s3: leases - oplock break errors

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

diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c
index abe6709..f9a69a5 100644
--- a/source3/smbd/smb2_break.c
+++ b/source3/smbd/smb2_break.c
@@ -42,6 +42,8 @@ NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req)
 	NTSTATUS status;
 	const uint8_t *inbody;
 	uint8_t in_oplock_level;
+	uint8_t open_oplock;
+	bool breaking;
 	uint64_t in_file_id_persistent;
 	uint64_t in_file_id_volatile;
 	struct files_struct *in_fsp;
@@ -71,15 +73,35 @@ 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) {
+	breaking = (in_fsp->oplock_timeout != NULL);
+	open_oplock = in_fsp->oplock_type;
+
+	if ((in_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) && !breaking) {
+		return smbd_smb2_request_error(
+			req, NT_STATUS_INVALID_OPLOCK_PROTOCOL);
+	}
+
+	if (((open_oplock == OPLOCK_BATCH) ||
+	     (open_oplock == OPLOCK_EXCLUSIVE)) &&
+	    (!((in_oplock_level == SMB2_OPLOCK_LEVEL_II) ||
+	       (in_oplock_level == SMB2_OPLOCK_LEVEL_NONE))) &&
+	    !breaking) {
 		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);
+	if ((open_oplock == OPLOCK_LEVEL_II) &&
+	    (in_oplock_level != SMB2_OPLOCK_LEVEL_NONE) &&
+	    !breaking) {
+		return smbd_smb2_request_error(
+			req, NT_STATUS_INVALID_OPLOCK_PROTOCOL);
+	}
+
+	if (((in_oplock_level == SMB2_OPLOCK_LEVEL_II) ||
+	     (in_oplock_level == SMB2_OPLOCK_LEVEL_NONE)) &&
+	    !breaking) {
+		return smbd_smb2_request_error(
+			req, NT_STATUS_INVALID_DEVICE_STATE);
 	}
 
 	subreq = smbd_smb2_oplock_break_send(req, req->sconn->ev_ctx,
-- 
2.1.0.rc2.206.gedb03e5


From 7d30bb44add8bc06dfad38ad839e976f71941964 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 12/21] 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 ad17ed0..f1bddcd 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;
 
-- 
2.1.0.rc2.206.gedb03e5


From f4d3e4a202ce9854ca04a764a9d85cff14552455 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 13/21] 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;
 }
-- 
2.1.0.rc2.206.gedb03e5


From 7204a450a005c36a9b382d5c57a14df47cfd4b7d 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 14/21] 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);
-- 
2.1.0.rc2.206.gedb03e5


From fde539b531ed7536d81f9bfeb538fb9417580549 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 15/21] 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 | 133 ++++++++++++++++++++++++++++------------------
 1 file changed, 81 insertions(+), 52 deletions(-)

diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index cf1b497..804c68f 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -618,6 +618,83 @@ 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, 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 = UINT16_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 +752,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 +852,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,66 +902,15 @@ 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, 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 = UINT16_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;
 }
 
-- 
2.1.0.rc2.206.gedb03e5


From fa582e9b57471783f31d61a1525b5b074dc8da77 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 16/21] 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 |   3 +-
 source3/locking/leases_db.c      | 159 +++++++++++++++++++++++++++++++++------
 source3/locking/leases_db.h      |   6 +-
 source3/locking/locking.c        |   3 +-
 source3/smbd/open.c              |   3 +-
 5 files changed, 148 insertions(+), 26 deletions(-)

diff --git a/source3/librpc/idl/leases_db.idl b/source3/librpc/idl/leases_db.idl
index 9ec8912..28ec479 100644
--- a/source3/librpc/idl/leases_db.idl
+++ b/source3/librpc/idl/leases_db.idl
@@ -16,7 +16,8 @@ interface leases_db
 	} leases_db_key;
 
 	typedef [public] struct {
-		file_id id;
+		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
index 69b1362..29b8e35 100644
--- a/source3/locking/leases_db.c
+++ b/source3/locking/leases_db.c
@@ -89,11 +89,12 @@ NTSTATUS leases_db_add(const struct GUID *client_guid,
 		       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 value;
-	DATA_BLOB blob;
+	struct leases_db_value new_value;
+	struct leases_db_value *value = NULL;
 	enum ndr_err_code ndr_err;
 
 	if (!leases_db_init(false)) {
@@ -114,25 +115,64 @@ NTSTATUS leases_db_add(const struct GUID *client_guid,
 
 	db_value = dbwrap_record_get_value(rec);
 	if (db_value.dsize != 0) {
+		uint32_t i;
+
 		DEBUG(10, ("%s: record exists\n", __func__));
-		TALLOC_FREE(rec);
-		return NT_STATUS_OBJECT_NAME_COLLISION;
-	}
 
-	value = (struct leases_db_value) {
-		.id = *id,
-		.filename = filename,
-		.stream_name = stream_name,
-	};
+		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,
+		&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)));
-		TALLOC_FREE(rec);
-		return ndr_map_error2ntstatus(ndr_err);
+		status = ndr_map_error2ntstatus(ndr_err);
+		goto out;
 	}
 
 	db_value = make_tdb_data(blob.data, blob.length);
@@ -143,16 +183,26 @@ NTSTATUS leases_db_add(const struct GUID *client_guid,
 			   __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 smb2_lease_key *lease_key,
+		       const struct file_id *id)
 {
-	TDB_DATA db_key;
+	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)) {
@@ -169,14 +219,79 @@ NTSTATUS leases_db_del(const struct GUID *client_guid,
 	if (rec == NULL) {
 		return NT_STATUS_NOT_FOUND;
 	}
-	status = dbwrap_record_delete(rec);
+	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)(struct file_id id, const char *filename,
-		       const char *stream_name, void *private_data);
+	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;
 };
@@ -206,8 +321,9 @@ static void leases_db_parser(TDB_DATA key, TDB_DATA data, void *private_data)
 		return;
 	}
 
-	state->parser(value->id, value->filename, value->stream_name,
-		      state->private_data);
+	state->parser(value->num_file_ids,
+			value->ids, value->filename, value->stream_name,
+			state->private_data);
 
 	TALLOC_FREE(value);
 	state->status = NT_STATUS_OK;
@@ -215,7 +331,8 @@ static void leases_db_parser(TDB_DATA key, TDB_DATA data, void *private_data)
 
 NTSTATUS leases_db_parse(const struct GUID *client_guid,
 			 const struct smb2_lease_key *lease_key,
-			 void (*parser)(struct file_id id,
+			 void (*parser)(uint32_t num_file_ids,
+					struct file_id *ids,
 					const char *filename,
 					const char *stream_name,
 					void *private_data),
diff --git a/source3/locking/leases_db.h b/source3/locking/leases_db.h
index ff9c362..f570356 100644
--- a/source3/locking/leases_db.h
+++ b/source3/locking/leases_db.h
@@ -32,10 +32,12 @@ NTSTATUS leases_db_add(const struct GUID *client_guid,
 		       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 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)(struct file_id id,
+			 void (*parser)(uint32_t num_file_ids,
+					struct file_id *ids,
 					const char *filename,
 					const char *stream_name,
 					void *private_data),
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
index 804c68f..1df9133 100644
--- a/source3/locking/locking.c
+++ b/source3/locking/locking.c
@@ -687,7 +687,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)));
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index a5a8dd0..358c6bf 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -4089,7 +4089,8 @@ struct lease_fname_match_state {
 };
 
 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 =
-- 
2.1.0.rc2.206.gedb03e5


From 2bc1bdce34b774cb653204af9355234f1d57cbc3 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 17/21] s3: smbd: leases - Fix the dynamic share file case
 [homes].

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

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 358c6bf..606da58 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -4084,8 +4084,14 @@ 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(
@@ -4096,28 +4102,130 @@ static void lease_fname_match_parser(
 	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;
+	}
+	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);
 	}
-	return state.match;
+	/*
+	 * Ensure we don't grant anything more so we
+	 * never upgrade.
+	 */
+	return NT_STATUS_OPLOCK_NOT_GRANTED;
 }
 
 /*
@@ -4176,10 +4284,17 @@ 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... */
+			lease->lease_state = SMB2_LEASE_NONE;
+		} else if (!NT_STATUS_IS_OK(status)) {
+			goto fail;
+		}
 	}
 
 	if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
-- 
2.1.0.rc2.206.gedb03e5


From 711fb799ac0ade0734db317335698ed0ab1cb2a5 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 18/21] 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 606da58..ff77f3c 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);
-- 
2.1.0.rc2.206.gedb03e5


From 4e1cfcb2e41f42835c572762742d2334cc837737 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 19/21] 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 ff77f3c..b4525ab 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;
 	}
-- 
2.1.0.rc2.206.gedb03e5


From a6e81b0a8d14a832db5de7e60c3bfb4a7f30067a 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 20/21] 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 +++++++++
 source3/param/loadparm.c                   |  1 +
 source3/smbd/smb2_negprot.c                |  2 +-
 4 files changed, 30 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/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;
 	}
 
-- 
2.1.0.rc2.206.gedb03e5


From 887b3954e91c0765c913ae93d4dcc0d2cb23b304 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 21/21] 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".

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                          |  3 ++-
 5 files changed, 38 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 de40ced..ebe2c09 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1073,6 +1073,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..10224d8 100644
--- a/source3/smbd/dir.c
+++ b/source3/smbd/dir.c
@@ -1966,7 +1966,8 @@ NTSTATUS can_delete_directory_fsp(files_struct *fsp)
 		return status;
 	}
 
-	if (have_file_open_below(fsp->conn, fsp->fsp_name)) {
+	if (lp_strict_rename(SNUM(conn)) &&
+			have_file_open_below(fsp->conn, fsp->fsp_name)) {
 		return NT_STATUS_ACCESS_DENIED;
 	}
 
-- 
2.1.0.rc2.206.gedb03e5



More information about the samba-technical mailing list