diff options
Diffstat (limited to 'source4')
-rw-r--r-- | source4/libcli/raw/interfaces.h | 23 | ||||
-rw-r--r-- | source4/libcli/smb2/config.mk | 3 | ||||
-rw-r--r-- | source4/libcli/smb2/create.c | 4 | ||||
-rw-r--r-- | source4/libcli/smb2/lease_break.c | 81 | ||||
-rw-r--r-- | source4/libcli/smb2/smb2.h | 13 | ||||
-rw-r--r-- | source4/libcli/smb2/transport.c | 55 | ||||
-rw-r--r-- | source4/torture/smb2/durable_open.c | 420 | ||||
-rw-r--r-- | source4/torture/smb2/lease.c | 704 |
8 files changed, 1155 insertions, 148 deletions
diff --git a/source4/libcli/raw/interfaces.h b/source4/libcli/raw/interfaces.h index bd93fa1695..3c0d186b87 100644 --- a/source4/libcli/raw/interfaces.h +++ b/source4/libcli/raw/interfaces.h @@ -56,13 +56,26 @@ struct smb2_handle { /* SMB2 lease structure (per MS-SMB2 2.2.13) */ +struct smb2_lease_key { + uint64_t data[2]; +}; + struct smb2_lease { - uint64_t lease_key[2]; + struct smb2_lease_key lease_key; uint32_t lease_state; uint32_t lease_flags; /* should be 0 */ uint64_t lease_duration; /* should be 0 */ }; +struct smb2_lease_break { + struct smb2_lease current_lease; + uint32_t break_flags; + uint32_t new_lease_state; + uint32_t break_reason; /* should be 0 */ + uint32_t access_mask_hint; /* should be 0 */ + uint32_t share_mask_hint; /* should be 0 */ +}; + struct ntvfs_handle; /* @@ -2006,6 +2019,14 @@ union smb_lock { /* struct smb2_handle handle; */ } in, out; } smb2_break; + + /* SMB2 Lease Break Ack (same opcode as smb2_break) */ + struct smb2_lease_break_ack { + struct { + uint32_t reserved; + struct smb2_lease lease; + } in, out; + } smb2_lease_break_ack; }; diff --git a/source4/libcli/smb2/config.mk b/source4/libcli/smb2/config.mk index 322bca1416..ddd45c965f 100644 --- a/source4/libcli/smb2/config.mk +++ b/source4/libcli/smb2/config.mk @@ -5,6 +5,7 @@ LIBCLI_SMB2_OBJ_FILES = $(addprefix $(libclisrcdir)/smb2/, \ transport.o request.o negprot.o session.o tcon.o \ create.o close.o connect.o getinfo.o write.o read.o \ setinfo.o find.o ioctl.o logoff.o tdis.o flush.o \ - lock.o notify.o cancel.o keepalive.o break.o util.o signing.o) + lock.o notify.o cancel.o keepalive.o break.o util.o signing.o \ + lease_break.o) $(eval $(call proto_header_template,$(libclisrcdir)/smb2/smb2_proto.h,$(LIBCLI_SMB2_OBJ_FILES:.o=.c))) diff --git a/source4/libcli/smb2/create.c b/source4/libcli/smb2/create.c index 344be60f6e..363210bd03 100644 --- a/source4/libcli/smb2/create.c +++ b/source4/libcli/smb2/create.c @@ -315,7 +315,7 @@ struct smb2_request *smb2_create_send(struct smb2_tree *tree, struct smb2_create if (io->in.lease_request) { uint8_t data[32]; - memcpy(&data[0], io->in.lease_request->lease_key, 16); + memcpy(&data[0], &io->in.lease_request->lease_key, 16); SIVAL(data, 16, io->in.lease_request->lease_state); SIVAL(data, 20, io->in.lease_request->lease_flags); SBVAL(data, 24, io->in.lease_request->lease_duration); @@ -427,7 +427,7 @@ NTSTATUS smb2_create_recv(struct smb2_request *req, TALLOC_CTX *mem_ctx, struct } data = io->out.blobs.blobs[i].data.data; - memcpy(io->out.lease_response.lease_key, data, 16); + memcpy(&io->out.lease_response.lease_key, data, 16); io->out.lease_response.lease_state = IVAL(data, 16); io->out.lease_response.lease_flags = IVAL(data, 20); io->out.lease_response.lease_duration = BVAL(data, 24); diff --git a/source4/libcli/smb2/lease_break.c b/source4/libcli/smb2/lease_break.c new file mode 100644 index 0000000000..c238f1d8ab --- /dev/null +++ b/source4/libcli/smb2/lease_break.c @@ -0,0 +1,81 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client oplock break handling + + Copyright (C) Zachary Loafman 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +/* + Send a Lease Break Acknowledgement +*/ +struct smb2_request *smb2_lease_break_ack_send(struct smb2_tree *tree, + struct smb2_lease_break_ack *io) +{ + struct smb2_request *req; + + req = smb2_request_init_tree(tree, SMB2_OP_BREAK, 0x24, false, 0); + if (req == NULL) return NULL; + + SIVAL(req->out.body, 0x02, io->in.reserved); + SIVAL(req->out.body, 0x04, io->in.lease.lease_flags); + memcpy(req->out.body+0x8, &io->in.lease.lease_key, + sizeof(struct smb2_lease_key)); + SIVAL(req->out.body, 0x18, io->in.lease.lease_state); + SBVAL(req->out.body, 0x1C, io->in.lease.lease_duration); + + smb2_transport_send(req); + + return req; +} + + +/* + Receive a Lease Break Response +*/ +NTSTATUS smb2_lease_break_ack_recv(struct smb2_request *req, + struct smb2_lease_break_ack *io) +{ + if (!smb2_request_receive(req) || + !smb2_request_is_ok(req)) { + return smb2_request_destroy(req); + } + + SMB2_CHECK_PACKET_RECV(req, 0x24, false); + + io->out.reserved = IVAL(req->in.body, 0x02); + io->out.lease.lease_flags = IVAL(req->in.body, 0x04); + memcpy(&io->out.lease.lease_key, req->in.body+0x8, + sizeof(struct smb2_lease_key)); + io->out.lease.lease_state = IVAL(req->in.body, 0x18); + io->out.lease.lease_duration = IVAL(req->in.body, 0x1C); + + return smb2_request_destroy(req); +} + +/* + sync flush request +*/ +NTSTATUS smb2_lease_break_ack(struct smb2_tree *tree, + struct smb2_lease_break_ack *io) +{ + struct smb2_request *req = smb2_lease_break_ack_send(tree, io); + return smb2_lease_break_ack_recv(req, io); +} diff --git a/source4/libcli/smb2/smb2.h b/source4/libcli/smb2/smb2.h index fd961ce5f3..3044623ae8 100644 --- a/source4/libcli/smb2/smb2.h +++ b/source4/libcli/smb2/smb2.h @@ -26,6 +26,7 @@ #include "libcli/raw/libcliraw.h" struct smb2_handle; +struct smb2_lease_break; /* information returned from the negotiate process @@ -73,6 +74,15 @@ struct smb2_transport { void *private_data; } oplock; + struct { + /* a lease break request handler */ + bool (*handler)(struct smb2_transport *transport, + const struct smb2_lease_break *lease_break, + void *private_data); + /* private data passed to the oplock handler */ + void *private_data; + } lease; + struct smbcli_options options; bool signing_required; @@ -271,6 +281,9 @@ struct smb2_request { #define SMB2_LEASE_HANDLE 0x02 #define SMB2_LEASE_WRITE 0x04 +/* SMB2 lease break flags */ +#define SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED 0x01 + /* SMB2 impersonation levels */ #define SMB2_IMPERSONATION_ANONYMOUS 0x00 #define SMB2_IMPERSONATION_IDENTIFICATION 0x01 diff --git a/source4/libcli/smb2/transport.c b/source4/libcli/smb2/transport.c index e112544c62..6a87d124d9 100644 --- a/source4/libcli/smb2/transport.c +++ b/source4/libcli/smb2/transport.c @@ -144,24 +144,39 @@ static NTSTATUS smb2_handle_oplock_break(struct smb2_transport *transport, const DATA_BLOB *blob) { uint8_t *hdr; - uint16_t opcode; + uint8_t *body; + uint16_t len, bloblen; + bool lease; hdr = blob->data+NBT_HDR_SIZE; + body = hdr+SMB2_HDR_BODY; + bloblen = blob->length - SMB2_HDR_BODY; - if (blob->length < (SMB2_MIN_SIZE+0x18)) { + if (bloblen < 2) { DEBUG(1,("Discarding smb2 oplock reply of size %u\n", - (unsigned)blob->length)); + (unsigned)blob->length)); return NT_STATUS_INVALID_NETWORK_RESPONSE; } - opcode = SVAL(hdr, SMB2_HDR_OPCODE); + len = CVAL(body, 0x00); + if (len > bloblen) { + DEBUG(1,("Discarding smb2 oplock reply," + "packet claims %u byte body, only %u bytes seen\n", + len, bloblen)); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } - if (opcode != SMB2_OP_BREAK) { + if (len == 24) { + lease = false; + } else if (len == 44) { + lease = true; + } else { + DEBUG(1,("Discarding smb2 oplock reply of invalid size %u\n", + (unsigned)blob->length)); return NT_STATUS_INVALID_NETWORK_RESPONSE; } - if (transport->oplock.handler) { - uint8_t *body = hdr+SMB2_HDR_BODY; + if (!lease && transport->oplock.handler) { struct smb2_handle h; uint8_t level; @@ -170,8 +185,24 @@ static NTSTATUS smb2_handle_oplock_break(struct smb2_transport *transport, transport->oplock.handler(transport, &h, level, transport->oplock.private_data); + } else if (lease && transport->lease.handler) { + struct smb2_lease_break lb; + + ZERO_STRUCT(lb); + lb.break_flags = SVAL(body, 0x4); + memcpy(&lb.current_lease.lease_key, body+0x8, + sizeof(struct smb2_lease_key)); + lb.current_lease.lease_state = SVAL(body, 0x18); + lb.new_lease_state = SVAL(body, 0x1C); + lb.break_reason = SVAL(body, 0x20); + lb.access_mask_hint = SVAL(body, 0x24); + lb.share_mask_hint = SVAL(body, 0x28); + + transport->lease.handler(transport, &lb, + transport->lease.private_data); } else { - DEBUG(5,("Got SMB2 oplock break with no handler\n")); + DEBUG(5,("Got SMB2 %s break with no handler\n", + lease ? "lease" : "oplock")); } return NT_STATUS_OK; @@ -193,6 +224,7 @@ static NTSTATUS smb2_transport_finish_recv(void *private_data, DATA_BLOB blob) uint16_t buffer_code; uint32_t dynamic_size; uint32_t i; + uint16_t opcode; NTSTATUS status; buffer = blob.data; @@ -207,9 +239,16 @@ static NTSTATUS smb2_transport_finish_recv(void *private_data, DATA_BLOB blob) flags = IVAL(hdr, SMB2_HDR_FLAGS); seqnum = BVAL(hdr, SMB2_HDR_MESSAGE_ID); + opcode = SVAL(hdr, SMB2_HDR_OPCODE); /* see MS-SMB2 3.2.5.19 */ if (seqnum == UINT64_MAX) { + if (opcode != SMB2_OP_BREAK) { + DEBUG(1,("Discarding packet with invalid seqnum, " + "opcode %u\n", opcode)); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + return smb2_handle_oplock_break(transport, &blob); } diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index 9cc25e3408..1b86f2c46e 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -41,6 +41,15 @@ goto done; \ }} while (0) +#define CHECK_CREATED(__io, __created, __attribute) \ + do { \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ + 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); \ + } while(0) + /* basic testing of SMB2 durable opens regarding the position information on the handle @@ -54,7 +63,6 @@ bool test_durable_open_file_position(struct torture_context *tctx, struct smb2_create io1, io2; NTSTATUS status; const char *fname = "durable_open_position.dat"; - DATA_BLOB b; union smb_fileinfo qfinfo; union smb_setfileinfo sfinfo; bool ret = true; @@ -78,31 +86,17 @@ bool test_durable_open_file_position(struct torture_context *tctx, NTCREATEX_OPTIONS_ASYNC_ALERT | NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | 0x00200000; + io1.in.durable_open = true; io1.in.fname = fname; - b = data_blob_talloc(mem_ctx, NULL, 16); - SBVAL(b.data, 0, 0); - SBVAL(b.data, 8, 0); - - status = smb2_create_blob_add(tree1, &io1.in.blobs, - SMB2_CREATE_TAG_DHNQ, - b); - CHECK_STATUS(status, NT_STATUS_OK); - status = smb2_create(tree1, mem_ctx, &io1); CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); - /*CHECK_VAL(io1.out.reserved, 0);*/ - CHECK_VAL(io1.out.create_action, NTCREATEX_ACTION_CREATED); - CHECK_VAL(io1.out.alloc_size, 0); - CHECK_VAL(io1.out.size, 0); - CHECK_VAL(io1.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); - CHECK_VAL(io1.out.reserved2, 0); /* TODO: check extra blob content */ - h1 = io1.out.file.handle; - ZERO_STRUCT(qfinfo); qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION; qfinfo.generic.in.file.handle = h1; @@ -141,15 +135,7 @@ bool test_durable_open_file_position(struct torture_context *tctx, ZERO_STRUCT(io2); io2.in.fname = fname; - - b = data_blob_talloc(tctx, NULL, 16); - SBVAL(b.data, 0, h1.data[0]); - SBVAL(b.data, 8, h1.data[1]); - - status = smb2_create_blob_add(tree2, &io2.in.blobs, - SMB2_CREATE_TAG_DHNC, - b); - CHECK_STATUS(status, NT_STATUS_OK); + io2.in.durable_handle = &h1; status = smb2_create(tree2, mem_ctx, &io2); CHECK_STATUS(status, NT_STATUS_OK); @@ -191,12 +177,14 @@ bool test_durable_open_oplock(struct torture_context *tctx, { TALLOC_CTX *mem_ctx = talloc_new(tctx); struct smb2_create io1, io2; - struct smb2_handle h1; + struct smb2_handle h1, h2; NTSTATUS status; - const char *fname = "durable_open_oplock.dat"; - DATA_BLOB b; + char fname[256]; bool ret = true; + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8)); + /* Clean slate */ smb2_util_unlink(tree1, fname); @@ -218,29 +206,16 @@ bool test_durable_open_oplock(struct torture_context *tctx, NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | 0x00200000; io1.in.fname = fname; + io1.in.durable_open = true; io2 = io1; io2.in.create_disposition = NTCREATEX_DISP_OPEN; - b = data_blob_talloc(mem_ctx, NULL, 16); - SBVAL(b.data, 0, 0); - SBVAL(b.data, 8, 0); - - status = smb2_create_blob_add(tree1, &io1.in.blobs, - SMB2_CREATE_TAG_DHNQ, - b); - CHECK_STATUS(status, NT_STATUS_OK); - status = smb2_create(tree1, mem_ctx, &io1); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); - CHECK_VAL(io1.out.create_action, NTCREATEX_ACTION_CREATED); - CHECK_VAL(io1.out.alloc_size, 0); - CHECK_VAL(io1.out.size, 0); - CHECK_VAL(io1.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); - CHECK_VAL(io1.out.reserved2, 0); - h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); /* Disconnect after getting the batch */ talloc_free(tree1); @@ -253,12 +228,9 @@ bool test_durable_open_oplock(struct torture_context *tctx, */ status = smb2_create(tree2, mem_ctx, &io2); CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH); - CHECK_VAL(io2.out.create_action, NTCREATEX_ACTION_EXISTED); - CHECK_VAL(io2.out.alloc_size, 0); - CHECK_VAL(io2.out.size, 0); - CHECK_VAL(io2.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); - CHECK_VAL(io2.out.reserved2, 0); /* What if tree1 tries to come back and reclaim? */ if (!torture_smb2_connection(tctx, &tree1)) { @@ -267,24 +239,349 @@ bool test_durable_open_oplock(struct torture_context *tctx, goto done; } - ZERO_STRUCT(io2); - io2.in.fname = fname; + ZERO_STRUCT(io1); + io1.in.fname = fname; + io1.in.durable_handle = &h1; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + done: + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + + return ret; +} + +/* + Open, disconnect, lease break, reconnect. +*/ +bool test_durable_open_lease(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1, io2; + struct smb2_lease ls1, ls2; + struct smb2_handle h1, h2; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease1, lease2; + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease1 = random(); + lease2 = random(); + snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree1, fname); - b = data_blob_talloc(tctx, NULL, 16); - SBVAL(b.data, 0, h1.data[0]); - SBVAL(b.data, 8, h1.data[1]); + /* Create with lease */ + ZERO_STRUCT(io1); + io1.in.security_flags = 0x00; + io1.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + io1.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + io1.in.create_flags = 0x00000000; + io1.in.reserved = 0x00000000; + io1.in.desired_access = SEC_RIGHTS_FILE_ALL; + io1.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io1.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io1.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + io1.in.fname = fname; + io1.in.durable_open = true; + + ZERO_STRUCT(ls1); + ls1.lease_key.data[0] = lease1; + ls1.lease_key.data[1] = ~lease1; + ls1.lease_state = SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE; + io1.in.lease_request = &ls1; + + io2 = io1; + ls2 = ls1; + ls2.lease_key.data[0] = lease2; + ls2.lease_key.data[1] = ~lease2; + io2.in.lease_request = &ls2; + io2.in.create_disposition = NTCREATEX_DISP_OPEN; - status = smb2_create_blob_add(tree2, &io2.in.blobs, - SMB2_CREATE_TAG_DHNC, - b); + status = smb2_create(tree1, mem_ctx, &io1); CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease1); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease1); + CHECK_VAL(io1.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + + /* Disconnect after getting the lease */ + talloc_free(tree1); + tree1 = NULL; + /* + * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?) + * even if the original client is gone. (ZML: This seems like a bug. It + * should give some time for the client to reconnect! And why RH?) + */ status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io2.out.lease_response.lease_key.data[0], lease2); + CHECK_VAL(io2.out.lease_response.lease_key.data[1], ~lease2); + CHECK_VAL(io2.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE); + + /* What if tree1 tries to come back and reclaim? */ + if (!torture_smb2_connection(tctx, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io1); + io1.in.fname = fname; + io1.in.durable_handle = &h1; + io1.in.lease_request = &ls1; + + status = smb2_create(tree1, mem_ctx, &io1); CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); -done: + done: + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + return ret; +} +/* + Open, take BRL, disconnect, reconnect. +*/ +bool test_durable_open_lock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease = random(); + snprintf(fname, 256, "durable_open_lock_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree, fname); + + /* Create with lease */ + ZERO_STRUCT(io); + io.in.security_flags = 0x00; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + io.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + io.in.create_flags = 0x00000000; + io.in.reserved = 0x00000000; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + io.in.fname = fname; + io.in.durable_open = true; + + ZERO_STRUCT(ls); + ls.lease_key.data[0] = lease; + ls.lease_key.data[1] = ~lease; + ls.lease_state = SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE; + io.in.lease_request = &ls; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease); + CHECK_VAL(io.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.reserved = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Disconnect/Reconnect. */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection(tctx, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = &h; + io.in.lease_request = &ls; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + + return ret; +} + +/* + Open, disconnect, open in another tree, reconnect. + + This test actually demonstrates a minimum level of respect for the durable + open in the face of another open. As long as this test shows an inability to + reconnect after an open, the oplock/lease tests above will certainly + demonstrate an error on reconnect. +*/ +bool test_durable_open_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io1, io2; + struct smb2_lease ls; + struct smb2_handle h1, h2; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease = random(); + snprintf(fname, 256, "durable_open_lock_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree1, fname); + + /* Create with lease */ + ZERO_STRUCT(io1); + io1.in.security_flags = 0x00; + io1.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + io1.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + io1.in.create_flags = 0x00000000; + io1.in.reserved = 0x00000000; + io1.in.desired_access = SEC_RIGHTS_FILE_ALL; + io1.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io1.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + io1.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io1.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + io1.in.fname = fname; + io1.in.durable_open = true; + + io2 = io1; + io2.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + io2.in.durable_open = false; + + ZERO_STRUCT(ls); + ls.lease_key.data[0] = lease; + ls.lease_key.data[1] = ~lease; + ls.lease_state = SMB2_LEASE_READ|SMB2_LEASE_HANDLE; + io1.in.lease_request = &ls; + + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io1.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease); + CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease); + CHECK_VAL(io1.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE); + + /* Disconnect */ + talloc_free(tree1); + tree1 = NULL; + + /* Open the file in tree2 */ + status = smb2_create(tree2, mem_ctx, &io2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io2.out.file.handle; + CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + /* Reconnect */ + if (!torture_smb2_connection(tctx, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io1); + io1.in.fname = fname; + io1.in.durable_handle = &h1; + io1.in.lease_request = &ls; + + /* + * Windows7 (build 7000) will give away an open immediately if the + * original client is gone. (ZML: This seems like a bug. It should give + * some time for the client to reconnect!) + */ + status = smb2_create(tree1, mem_ctx, &io1); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h1 = io1.out.file.handle; + + done: + smb2_util_close(tree2, h2); + smb2_util_unlink(tree2, fname); + smb2_util_close(tree1, h1); + smb2_util_unlink(tree1, fname); + + return ret; } struct torture_suite *torture_smb2_durable_open_init(void) @@ -295,6 +592,9 @@ struct torture_suite *torture_smb2_durable_open_init(void) torture_suite_add_2smb2_test(suite, "FILE-POSITION", test_durable_open_file_position); torture_suite_add_2smb2_test(suite, "OPLOCK", test_durable_open_oplock); + torture_suite_add_2smb2_test(suite, "LEASE", test_durable_open_lease); + torture_suite_add_1smb2_test(suite, "LOCK", test_durable_open_lock); + torture_suite_add_2smb2_test(suite, "OPEN", test_durable_open_open); suite->description = talloc_strdup(suite, "SMB2-DURABLE-OPEN tests"); diff --git a/source4/torture/smb2/lease.c b/source4/torture/smb2/lease.c index e67517324b..87526e3f78 100644 --- a/source4/torture/smb2/lease.c +++ b/source4/torture/smb2/lease.c @@ -20,16 +20,38 @@ */ #include "includes.h" +#include "lib/events/events.h" #include "librpc/gen_ndr/security.h" #include "libcli/smb2/smb2.h" #include "libcli/smb2/smb2_calls.h" #include "torture/torture.h" #include "torture/smb2/proto.h" +static inline uint32_t lease(const char *ls) { + uint32_t val = 0; + int i; + + for (i = 0; i < strlen(ls); i++) { + switch (ls[i]) { + case 'R': + val |= SMB2_LEASE_READ; + break; + case 'H': + val |= SMB2_LEASE_HANDLE; + break; + case 'W': + val |= SMB2_LEASE_WRITE; + break; + } + } + + return val; +} + #define CHECK_VAL(v, correct) do { \ if ((v) != (correct)) { \ torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \ - __location__, #v, (int)v, (int)correct); \ + __location__, #v, (int)(v), (int)(correct)); \ ret = false; \ }} while (0) @@ -41,13 +63,14 @@ goto done; \ }} while (0) -static void smb2_lease_create(struct smb2_create *io, struct smb2_lease *ls, - bool dir, const char *name, uint64_t leasekey, - uint32_t leasestate) +static void smb2_generic_create(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint32_t disposition, + uint32_t oplock, uint64_t leasekey, + uint32_t leasestate) { ZERO_STRUCT(*io); io->in.security_flags = 0x00; - io->in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + io->in.oplock_level = oplock; io->in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; io->in.create_flags = 0x00000000; io->in.reserved = 0x00000000; @@ -56,7 +79,7 @@ static void smb2_lease_create(struct smb2_create *io, struct smb2_lease *ls, io->in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; - io->in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io->in.create_disposition = disposition; io->in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | NTCREATEX_OPTIONS_ASYNC_ALERT | NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | @@ -66,24 +89,37 @@ static void smb2_lease_create(struct smb2_create *io, struct smb2_lease *ls, if (dir) { io->in.create_options = NTCREATEX_OPTIONS_DIRECTORY; io->in.share_access &= ~NTCREATEX_SHARE_ACCESS_DELETE; - io->in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io->in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; io->in.create_disposition = NTCREATEX_DISP_CREATE; } - ZERO_STRUCT(*ls); - ls->lease_key[0] = leasekey; - ls->lease_key[1] = ~leasekey; - ls->lease_state = leasestate; - io->in.lease_request = ls; + if (ls) { + ZERO_STRUCT(*ls); + ls->lease_key.data[0] = leasekey; + ls->lease_key.data[1] = ~leasekey; + ls->lease_state = leasestate; + io->in.lease_request = ls; + } +} + +static void smb2_lease_create(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint64_t leasekey, + uint32_t leasestate) +{ + smb2_generic_create(io, ls, dir, name, NTCREATEX_DISP_OPEN_IF, + SMB2_OPLOCK_LEVEL_LEASE, leasekey, leasestate); +} + +static void smb2_oplock_create(struct smb2_create *io, const char *name, + uint32_t oplock) +{ + smb2_generic_create(io, NULL, false, name, NTCREATEX_DISP_OPEN_IF, + oplock, 0, 0); } #define CHECK_CREATED(__io, __created, __attribute) \ do { \ - if (__created) { \ - CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_CREATED); \ - } else { \ - CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_EXISTED); \ - } \ + CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ CHECK_VAL((__io)->out.alloc_size, 0); \ CHECK_VAL((__io)->out.size, 0); \ CHECK_VAL((__io)->out.file_attr, (__attribute)); \ @@ -94,13 +130,13 @@ static void smb2_lease_create(struct smb2_create *io, struct smb2_lease *ls, do { \ if (__oplevel) { \ CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); \ - CHECK_VAL((__io)->out.lease_response.lease_key[0], (__key)); \ - CHECK_VAL((__io)->out.lease_response.lease_key[1], ~(__key)); \ - CHECK_VAL((__io)->out.lease_response.lease_state, (__state)); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[0], (__key)); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__io)->out.lease_response.lease_state, lease(__state)); \ } else { \ CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); \ - CHECK_VAL((__io)->out.lease_response.lease_key[0], 0); \ - CHECK_VAL((__io)->out.lease_response.lease_key[1], 0); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[0], 0); \ + CHECK_VAL((__io)->out.lease_response.lease_key.data[1], 0); \ CHECK_VAL((__io)->out.lease_response.lease_state, 0); \ } \ \ @@ -112,19 +148,16 @@ static const uint64_t LEASE1 = 0xBADC0FFEE0DDF00Dull; static const uint64_t LEASE2 = 0xDEADBEEFFEEDBEADull; static const uint64_t LEASE3 = 0xDAD0FFEDD00DF00Dull; -#define NRESULTS 8 -static const int request_results[NRESULTS][2] = { - { SMB2_LEASE_NONE, SMB2_LEASE_NONE }, - { SMB2_LEASE_READ, SMB2_LEASE_READ }, - { SMB2_LEASE_HANDLE, SMB2_LEASE_NONE, }, - { SMB2_LEASE_WRITE, SMB2_LEASE_NONE }, - { SMB2_LEASE_READ|SMB2_LEASE_HANDLE, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE }, - { SMB2_LEASE_READ|SMB2_LEASE_WRITE, - SMB2_LEASE_READ|SMB2_LEASE_WRITE }, - { SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE, SMB2_LEASE_NONE }, - { SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE }, +#define NREQUEST_RESULTS 8 +static const char *request_results[NREQUEST_RESULTS][2] = { + { "", "" }, + { "R", "R" }, + { "H", "" }, + { "W", "" }, + { "RH", "RH" }, + { "RW", "RW" }, + { "HW", "" }, + { "RHW", "RHW" }, }; static bool test_lease_request(struct torture_context *tctx, @@ -147,53 +180,49 @@ static bool test_lease_request(struct torture_context *tctx, smb2_util_rmdir(tree, dname); /* Win7 is happy to grant RHW leases on files. */ - smb2_lease_create(&io, &ls, false, fname, LEASE1, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RHW")); status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); h1 = io.out.file.handle; - CHECK_CREATED(&io, true, FILE_ATTRIBUTE_ARCHIVE); - CHECK_LEASE(&io, SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE, - true, LEASE1); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1); /* But will reject leases on directories. */ - smb2_lease_create(&io, &ls, true, dname, LEASE2, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + smb2_lease_create(&io, &ls, true, dname, LEASE2, lease("RHW")); status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io, true, FILE_ATTRIBUTE_DIRECTORY); - CHECK_LEASE(&io, SMB2_LEASE_NONE, false, 0); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_DIRECTORY); + CHECK_LEASE(&io, "", false, 0); smb2_util_close(tree, io.out.file.handle); /* Also rejects multiple files leased under the same key. */ - smb2_lease_create(&io, &ls, true, fname2, LEASE1, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + smb2_lease_create(&io, &ls, true, fname2, LEASE1, lease("RHW")); status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); /* And grants leases on streams (with separate leasekey). */ - smb2_lease_create(&io, &ls, false, sname, LEASE2, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + smb2_lease_create(&io, &ls, false, sname, LEASE2, lease("RHW")); status = smb2_create(tree, mem_ctx, &io); h2 = io.out.file.handle; CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io, true, FILE_ATTRIBUTE_ARCHIVE); - CHECK_LEASE(&io, SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE, - true, LEASE2); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE2); smb2_util_close(tree, h2); smb2_util_close(tree, h1); /* Now see what combos are actually granted. */ - for (i = 0; i < NRESULTS; i++) { - torture_comment(tctx, "Testing lease type %x, expecting %x\n", - request_results[i][0], request_results[i][1]); + for (i = 0; i < NREQUEST_RESULTS; i++) { + torture_comment(tctx, "Requesting lease type %s(%x)," + " expecting %s(%x)\n", + request_results[i][0], lease(request_results[i][0]), + request_results[i][1], lease(request_results[i][1])); smb2_lease_create(&io, &ls, false, fname, LEASE1, - request_results[i][0]); + lease(request_results[i][0])); status = smb2_create(tree, mem_ctx, &io); h2 = io.out.file.handle; CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io, false, FILE_ATTRIBUTE_ARCHIVE); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); CHECK_LEASE(&io, request_results[i][1], true, LEASE1); smb2_util_close(tree, io.out.file.handle); } @@ -225,48 +254,40 @@ static bool test_lease_upgrade(struct torture_context *tctx, smb2_util_unlink(tree, fname); /* Grab a RH lease. */ - smb2_lease_create(&io, &ls, false, fname, LEASE1, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE); + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RH")); status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io, true, FILE_ATTRIBUTE_ARCHIVE); - CHECK_LEASE(&io, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE, true, LEASE1); + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE1); h = io.out.file.handle; /* Upgrades (sidegrades?) to RW leave us with an RH. */ - smb2_lease_create(&io, &ls, false, fname, LEASE1, - SMB2_LEASE_READ|SMB2_LEASE_WRITE); + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RW")); status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io, false, FILE_ATTRIBUTE_ARCHIVE); - CHECK_LEASE(&io, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE, true, LEASE1); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE1); hnew = io.out.file.handle; smb2_util_close(tree, hnew); /* Upgrade to RHW lease. */ - smb2_lease_create(&io, &ls, false, fname, LEASE1, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE); + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RHW")); status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io, false, FILE_ATTRIBUTE_ARCHIVE); - CHECK_LEASE(&io, SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE, - true, LEASE1); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1); hnew = io.out.file.handle; smb2_util_close(tree, h); h = hnew; /* Attempt to downgrade - original lease state is maintained. */ - smb2_lease_create(&io, &ls, false, fname, LEASE1, - SMB2_LEASE_READ|SMB2_LEASE_HANDLE); + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RH")); status = smb2_create(tree, mem_ctx, &io); CHECK_STATUS(status, NT_STATUS_OK); - CHECK_CREATED(&io, false, FILE_ATTRIBUTE_ARCHIVE); - CHECK_LEASE(&io, SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE, - true, LEASE1); + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1); hnew = io.out.file.handle; smb2_util_close(tree, hnew); @@ -282,6 +303,534 @@ static bool test_lease_upgrade(struct torture_context *tctx, return ret; } +#define CHECK_LEASE_BREAK(__lb, __oldstate, __state, __key) \ + do { \ + CHECK_VAL((__lb)->new_lease_state, lease(__state)); \ + CHECK_VAL((__lb)->current_lease.lease_state, lease(__oldstate)); \ + CHECK_VAL((__lb)->current_lease.lease_key.data[0], (__key)); \ + CHECK_VAL((__lb)->current_lease.lease_key.data[1], ~(__key)); \ + } while(0) + +#define CHECK_LEASE_BREAK_ACK(__lba, __state, __key) \ + do { \ + CHECK_VAL((__lba)->out.reserved, 0); \ + CHECK_VAL((__lba)->out.lease.lease_key.data[0], (__key)); \ + CHECK_VAL((__lba)->out.lease.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__lba)->out.lease.lease_state, lease(__state)); \ + CHECK_VAL((__lba)->out.lease.lease_flags, 0); \ + CHECK_VAL((__lba)->out.lease.lease_duration, 0); \ + } while(0) + +static struct { + struct smb2_lease_break lease_break; + struct smb2_lease_break_ack lease_break_ack; + int count; + int failures; + + struct smb2_handle oplock_handle; + int held_oplock_level; + int oplock_level; + int oplock_count; + int oplock_failures; +} break_info; + +#define CHECK_BREAK_INFO(__oldstate, __state, __key) \ + do { \ + CHECK_VAL(break_info.failures, 0); \ + CHECK_VAL(break_info.count, 1); \ + CHECK_LEASE_BREAK(&break_info.lease_break, (__oldstate), \ + (__state), (__key)); \ + if (break_info.lease_break.break_flags & \ + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) { \ + CHECK_LEASE_BREAK_ACK(&break_info.lease_break_ack, \ + (__state), (__key)); \ + } \ + } while(0) + +static void torture_lease_break_callback(struct smb2_request *req) +{ + NTSTATUS status; + + status = smb2_lease_break_ack_recv(req, &break_info.lease_break_ack); + if (!NT_STATUS_IS_OK(status)) + break_info.failures++; + + return; +} + +/* a lease break request handler */ +static bool torture_lease_handler(struct smb2_transport *transport, + const struct smb2_lease_break *lb, + void *private_data) +{ + struct smb2_tree *tree = private_data; + struct smb2_lease_break_ack io; + struct smb2_request *req; + + break_info.lease_break = *lb; + break_info.count++; + + if (lb->break_flags & SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED) { + ZERO_STRUCT(io); + io.in.lease.lease_key = lb->current_lease.lease_key; + io.in.lease.lease_state = lb->new_lease_state; + + req = smb2_lease_break_ack_send(tree, &io); + req->async.fn = torture_lease_break_callback; + req->async.private_data = NULL; + } + + return true; +} + +/* + break_results should be read as "held lease, new lease, hold broken to, new + grant", i.e. { "RH", "RW", "RH", "R" } means that if key1 holds RH and key2 + tries for RW, key1 will be broken to RH (in this case, not broken at all) + and key2 will be granted R. + + Note: break_results only includes things that Win7 will actually grant (see + request_results above). + */ +#define NBREAK_RESULTS 16 +static const char *break_results[NBREAK_RESULTS][4] = { + {"R", "R", "R", "R"}, + {"R", "RH", "R", "RH"}, + {"R", "RW", "R", "R"}, + {"R", "RHW", "R", "RH"}, + + {"RH", "R", "RH", "R"}, + {"RH", "RH", "RH", "RH"}, + {"RH", "RW", "RH", "R"}, + {"RH", "RHW", "RH", "RH"}, + + {"RW", "R", "R", "R"}, + {"RW", "RH", "R", "RH"}, + {"RW", "RW", "R", "R"}, + {"RW", "RHW", "R", "RH"}, + + {"RHW", "R", "RH", "R"}, + {"RHW", "RH", "RH", "RH"}, + {"RHW", "RW", "RH", "R"}, + {"RHW", "RHW", "RH", "RH"}, +}; + +static bool test_lease_break(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h, h2, h3; + NTSTATUS status; + const char *fname = "lease.dat"; + bool ret = true; + int i; + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + + smb2_util_unlink(tree, fname); + + for (i = 0; i < NBREAK_RESULTS; i++) { + const char *held = break_results[i][0]; + const char *contend = break_results[i][1]; + const char *brokento = break_results[i][2]; + const char *granted = break_results[i][3]; + torture_comment(tctx, "Hold %s(%x), requesting %s(%x), " + "expecting break to %s(%x) and grant of %s(%x)\n", + held, lease(held), contend, lease(contend), + brokento, lease(brokento), granted, lease(granted)); + + ZERO_STRUCT(break_info); + + /* Grab lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease(held)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, held, true, LEASE1); + + /* Possibly contend lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE2, lease(contend)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, granted, true, LEASE2); + + if (lease(held) != lease(brokento)) { + CHECK_BREAK_INFO(held, brokento, LEASE1); + } else { + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + } + + ZERO_STRUCT(break_info); + + /* + Now verify that an attempt to upgrade LEASE1 results in no + break and no change in LEASE1. + */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, brokento, true, LEASE1); + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + } + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static void torture_oplock_break_callback(struct smb2_request *req) +{ + NTSTATUS status; + struct smb2_break br; + + ZERO_STRUCT(br); + status = smb2_break_recv(req, &br); + if (!NT_STATUS_IS_OK(status)) + break_info.oplock_failures++; + + return; +} + +/* a oplock break request handler */ +static bool torture_oplock_handler(struct smb2_transport *transport, + const struct smb2_handle *handle, + uint8_t level, void *private_data) +{ + struct smb2_tree *tree = private_data; + struct smb2_request *req; + struct smb2_break br; + + break_info.oplock_handle = *handle; + break_info.oplock_level = level; + break_info.oplock_count++; + + ZERO_STRUCT(br); + br.in.file.handle = *handle; + br.in.oplock_level = level; + + if (break_info.held_oplock_level > SMB2_OPLOCK_LEVEL_II) { + req = smb2_break_send(tree, &br); + req->async.fn = torture_oplock_break_callback; + req->async.private_data = NULL; + } + break_info.held_oplock_level = level; + + return true; +} + +static inline uint32_t oplock(const char *op) { + uint32_t val = SMB2_OPLOCK_LEVEL_NONE; + int i; + + for (i = 0; i < strlen(op); i++) { + switch (op[i]) { + case 's': + return SMB2_OPLOCK_LEVEL_II; + case 'x': + return SMB2_OPLOCK_LEVEL_EXCLUSIVE; + case 'b': + return SMB2_OPLOCK_LEVEL_EXCLUSIVE; + default: + continue; + } + } + + return val; +} + +#define NOPLOCK_RESULTS 12 +static const char *oplock_results[NOPLOCK_RESULTS][4] = { + {"R", "s", "R", "s"}, + {"R", "x", "R", "s"}, + {"R", "b", "R", "s"}, + + {"RH", "s", "RH", ""}, + {"RH", "x", "RH", ""}, + {"RH", "b", "RH", ""}, + + {"RW", "s", "R", "s"}, + {"RW", "x", "R", "s"}, + {"RW", "b", "R", "s"}, + + {"RHW", "s", "RH", ""}, + {"RHW", "x", "RH", ""}, + {"RHW", "b", "RH", ""}, +}; + +static const char *oplock_results_2[NOPLOCK_RESULTS][4] = { + {"s", "R", "s", "R"}, + {"s", "RH", "s", "R"}, + {"s", "RW", "s", "R"}, + {"s", "RHW", "s", "R"}, + + {"x", "R", "s", "R"}, + {"x", "RH", "s", "R"}, + {"x", "RW", "s", "R"}, + {"x", "RHW", "s", "R"}, + + {"b", "R", "s", "R"}, + {"b", "RH", "s", "R"}, + {"b", "RW", "s", "R"}, + {"b", "RHW", "s", "R"}, +}; + +static bool test_lease_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h, h2; + NTSTATUS status; + const char *fname = "lease.dat"; + bool ret = true; + int i; + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + smb2_util_unlink(tree, fname); + + for (i = 0; i < NOPLOCK_RESULTS; i++) { + const char *held = oplock_results[i][0]; + const char *contend = oplock_results[i][1]; + const char *brokento = oplock_results[i][2]; + const char *granted = oplock_results[i][3]; + torture_comment(tctx, "Hold %s(%x), requesting %s(%x), " + "expecting break to %s(%x) and grant of %s(%x)\n", + held, lease(held), contend, oplock(contend), + brokento, lease(brokento), granted, oplock(granted)); + + ZERO_STRUCT(break_info); + + /* Grab lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease(held)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, held, true, LEASE1); + + /* Does an oplock contend the lease? */ + smb2_oplock_create(&io, fname, oplock(contend)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, oplock(granted)); + break_info.held_oplock_level = io.out.oplock_level; + + if (lease(held) != lease(brokento)) { + CHECK_BREAK_INFO(held, brokento, LEASE1); + } else { + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + } + + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + } + + for (i = 0; i < NOPLOCK_RESULTS; i++) { + const char *held = oplock_results_2[i][0]; + const char *contend = oplock_results_2[i][1]; + const char *brokento = oplock_results_2[i][2]; + const char *granted = oplock_results_2[i][3]; + torture_comment(tctx, "Hold %s(%x), requesting %s(%x), " + "expecting break to %s(%x) and grant of %s(%x)\n", + held, oplock(held), contend, lease(contend), + brokento, oplock(brokento), granted, lease(granted)); + + ZERO_STRUCT(break_info); + + /* Grab an oplock. */ + smb2_oplock_create(&io, fname, oplock(held)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, oplock(held)); + break_info.held_oplock_level = io.out.oplock_level; + + /* Grab lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease(contend)); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, granted, true, LEASE1); + + if (oplock(held) != oplock(brokento)) { + CHECK_VAL(break_info.oplock_count, 1); + CHECK_VAL(break_info.oplock_failures, 0); + CHECK_VAL(break_info.oplock_level, oplock(brokento)); + break_info.held_oplock_level = break_info.oplock_level; + } else { + CHECK_VAL(break_info.oplock_count, 0); + CHECK_VAL(break_info.oplock_failures, 0); + } + + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + status = smb2_util_unlink(tree, fname); + CHECK_STATUS(status, NT_STATUS_OK); + } + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + +static bool test_lease_multibreak(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h, h2, h3; + struct smb2_write w; + NTSTATUS status; + const char *fname = "lease.dat"; + bool ret = true; + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + smb2_util_unlink(tree, fname); + + ZERO_STRUCT(break_info); + + /* Grab lease, upgrade to RHW .. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RH")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE1); + + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RHW", true, LEASE1); + + /* Contend with LEASE2. */ + smb2_lease_create(&io, &ls, false, fname, LEASE2, lease("RHW")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "RH", true, LEASE2); + + /* Verify that we were only sent one break. */ + CHECK_BREAK_INFO("RHW", "RH", LEASE1); + + /* Drop LEASE1 / LEASE2 */ + status = smb2_util_close(tree, h); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_close(tree, h2); + CHECK_STATUS(status, NT_STATUS_OK); + status = smb2_util_close(tree, h3); + CHECK_STATUS(status, NT_STATUS_OK); + + ZERO_STRUCT(break_info); + + /* Grab an R lease. */ + smb2_lease_create(&io, &ls, false, fname, LEASE1, lease("R")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE(&io, "R", true, LEASE1); + + /* Grab a level-II oplock. */ + smb2_oplock_create(&io, fname, oplock("s")); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, oplock("s")); + break_info.held_oplock_level = io.out.oplock_level; + + /* Verify no breaks. */ + CHECK_VAL(break_info.count, 0); + CHECK_VAL(break_info.failures, 0); + + /* Open for truncate, force a break. */ + smb2_generic_create(&io, NULL, false, fname, + NTCREATEX_DISP_OVERWRITE_IF, oplock(""), 0, 0); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h3 = io.out.file.handle; + CHECK_CREATED(&io, TRUNCATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.oplock_level, oplock("")); + break_info.held_oplock_level = io.out.oplock_level; + + /* Sleep, use a write to clear the recv queue. */ + msleep(250); + ZERO_STRUCT(w); + w.in.file.handle = h3; + w.in.offset = 0; + w.in.data = data_blob_talloc(mem_ctx, NULL, 4096); + status = smb2_write(tree, &w); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Verify one oplock break, one lease break. */ + CHECK_VAL(break_info.oplock_count, 1); + CHECK_VAL(break_info.oplock_failures, 0); + CHECK_VAL(break_info.oplock_level, oplock("")); + CHECK_BREAK_INFO("R", "", LEASE1); + + done: + smb2_util_close(tree, h); + smb2_util_close(tree, h2); + smb2_util_close(tree, h3); + + smb2_util_unlink(tree, fname); + + talloc_free(mem_ctx); + + return ret; +} + struct torture_suite *torture_smb2_lease_init(void) { struct torture_suite *suite = @@ -289,6 +838,9 @@ struct torture_suite *torture_smb2_lease_init(void) torture_suite_add_1smb2_test(suite, "REQUEST", test_lease_request); torture_suite_add_1smb2_test(suite, "UPGRADE", test_lease_upgrade); + torture_suite_add_1smb2_test(suite, "BREAK", test_lease_break); + torture_suite_add_1smb2_test(suite, "OPLOCK", test_lease_oplock); + torture_suite_add_1smb2_test(suite, "MULTIBREAK", test_lease_multibreak); suite->description = talloc_strdup(suite, "SMB2-LEASE tests"); |