2 Unix SMB/CIFS implementation.
4 test suite for SMB2 version two of durable opens
6 Copyright (C) Michael Adam 2012
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "libcli/smb2/smb2.h"
24 #include "libcli/smb2/smb2_calls.h"
25 #include "../libcli/smb/smbXcli_base.h"
26 #include "torture/torture.h"
27 #include "torture/smb2/proto.h"
28 #include "librpc/ndr/libndr.h"
30 #define CHECK_VAL(v, correct) do { \
31 if ((v) != (correct)) { \
32 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \
33 __location__, #v, (int)v, (int)correct); \
37 #define CHECK_STATUS(status, correct) do { \
38 if (!NT_STATUS_EQUAL(status, correct)) { \
39 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
40 nt_errstr(status), nt_errstr(correct)); \
45 #define CHECK_CREATED(__io, __created, __attribute) \
47 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
48 CHECK_VAL((__io)->out.alloc_size, 0); \
49 CHECK_VAL((__io)->out.size, 0); \
50 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
51 CHECK_VAL((__io)->out.reserved2, 0); \
59 static void torture_oplock_close_callback(struct smb2_request *req)
61 smb2_close_recv(req, &break_info.cl);
64 /* A general oplock break notification handler. This should be used when a
65 * test expects to break from batch or exclusive to a lower level. */
66 static bool torture_oplock_handler(struct smb2_transport *transport,
67 const struct smb2_handle *handle,
71 struct smb2_tree *tree = private_data;
72 struct smb2_request *req;
76 ZERO_STRUCT(break_info.cl);
77 break_info.cl.in.file.handle = *handle;
79 req = smb2_close_send(tree, &break_info.cl);
80 req->async.fn = torture_oplock_close_callback;
81 req->async.private_data = NULL;
86 * basic durable_open test.
87 * durable state should only be granted when requested
88 * along with a batch oplock or a handle lease.
90 * This test tests durable open with all possible oplock types.
93 struct durable_open_vs_oplock {
95 const char *share_mode;
100 #define NUM_OPLOCK_TYPES 4
101 #define NUM_SHARE_MODES 8
102 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
103 static struct durable_open_vs_oplock durable_open_vs_oplock_table[NUM_OPLOCK_OPEN_TESTS] =
105 { "", "", false, false },
106 { "", "R", false, false },
107 { "", "W", false, false },
108 { "", "D", false, false },
109 { "", "RD", false, false },
110 { "", "RW", false, false },
111 { "", "WD", false, false },
112 { "", "RWD", false, false },
114 { "s", "", false, false },
115 { "s", "R", false, false },
116 { "s", "W", false, false },
117 { "s", "D", false, false },
118 { "s", "RD", false, false },
119 { "s", "RW", false, false },
120 { "s", "WD", false, false },
121 { "s", "RWD", false, false },
123 { "x", "", false, false },
124 { "x", "R", false, false },
125 { "x", "W", false, false },
126 { "x", "D", false, false },
127 { "x", "RD", false, false },
128 { "x", "RW", false, false },
129 { "x", "WD", false, false },
130 { "x", "RWD", false, false },
132 { "b", "", true, false },
133 { "b", "R", true, false },
134 { "b", "W", true, false },
135 { "b", "D", true, false },
136 { "b", "RD", true, false },
137 { "b", "RW", true, false },
138 { "b", "WD", true, false },
139 { "b", "RWD", true, false },
142 static bool test_one_durable_v2_open_oplock(struct torture_context *tctx,
143 struct smb2_tree *tree,
145 bool request_persistent,
146 struct durable_open_vs_oplock test)
149 TALLOC_CTX *mem_ctx = talloc_new(tctx);
150 struct smb2_handle _h;
151 struct smb2_handle *h = NULL;
153 struct smb2_create io;
155 smb2_util_unlink(tree, fname);
157 smb2_oplock_create_share(&io, fname,
158 smb2_util_share_access(test.share_mode),
159 smb2_util_oplock_level(test.level));
160 io.in.durable_open = false;
161 io.in.durable_open_v2 = true;
162 io.in.persistent_open = request_persistent;
163 io.in.create_guid = GUID_random();
165 status = smb2_create(tree, mem_ctx, &io);
166 CHECK_STATUS(status, NT_STATUS_OK);
167 _h = io.out.file.handle;
169 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
170 CHECK_VAL(io.out.durable_open, false);
171 CHECK_VAL(io.out.durable_open_v2, test.durable);
172 CHECK_VAL(io.out.persistent_open, test.persistent);
173 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(test.level));
177 smb2_util_close(tree, *h);
179 smb2_util_unlink(tree, fname);
180 talloc_free(mem_ctx);
185 static bool test_durable_v2_open_oplock_table(struct torture_context *tctx,
186 struct smb2_tree *tree,
188 bool request_persistent,
189 struct durable_open_vs_oplock *table,
195 smb2_util_unlink(tree, fname);
197 for (i = 0; i < num_tests; i++) {
198 ret = test_one_durable_v2_open_oplock(tctx,
209 smb2_util_unlink(tree, fname);
214 bool test_durable_v2_open_oplock(struct torture_context *tctx,
215 struct smb2_tree *tree)
220 /* Choose a random name in case the state is left a little funky. */
221 snprintf(fname, 256, "durable_open_oplock_%s.dat",
222 generate_random_str(tctx, 8));
224 ret = test_durable_v2_open_oplock_table(tctx, tree, fname,
225 false, /* request_persistent */
226 durable_open_vs_oplock_table,
227 NUM_OPLOCK_OPEN_TESTS);
235 * basic durable handle open test.
236 * persistent state should only be granted when requested
237 * along with a batch oplock or a handle lease.
239 * This test tests persistent open with all valid lease types.
242 struct durable_open_vs_lease {
244 const char *share_mode;
249 #define NUM_LEASE_TYPES 5
250 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
251 static struct durable_open_vs_lease durable_open_vs_lease_table[NUM_LEASE_OPEN_TESTS] =
253 { "", "", false, false },
254 { "", "R", false, false },
255 { "", "W", false, false },
256 { "", "D", false, false },
257 { "", "RW", false, false },
258 { "", "RD", false, false },
259 { "", "WD", false, false },
260 { "", "RWD", false, false },
262 { "R", "", false, false },
263 { "R", "R", false, false },
264 { "R", "W", false, false },
265 { "R", "D", false, false },
266 { "R", "RW", false, false },
267 { "R", "RD", false, false },
268 { "R", "DW", false, false },
269 { "R", "RWD", false, false },
271 { "RW", "", false, false },
272 { "RW", "R", false, false },
273 { "RW", "W", false, false },
274 { "RW", "D", false, false },
275 { "RW", "RW", false, false },
276 { "RW", "RD", false, false },
277 { "RW", "WD", false, false },
278 { "RW", "RWD", false, false },
280 { "RH", "", true, false },
281 { "RH", "R", true, false },
282 { "RH", "W", true, false },
283 { "RH", "D", true, false },
284 { "RH", "RW", true, false },
285 { "RH", "RD", true, false },
286 { "RH", "WD", true, false },
287 { "RH", "RWD", true, false },
289 { "RHW", "", true, false },
290 { "RHW", "R", true, false },
291 { "RHW", "W", true, false },
292 { "RHW", "D", true, false },
293 { "RHW", "RW", true, false },
294 { "RHW", "RD", true, false },
295 { "RHW", "WD", true, false },
296 { "RHW", "RWD", true, false },
299 static bool test_one_durable_v2_open_lease(struct torture_context *tctx,
300 struct smb2_tree *tree,
302 bool request_persistent,
303 struct durable_open_vs_lease test)
306 TALLOC_CTX *mem_ctx = talloc_new(tctx);
307 struct smb2_handle _h;
308 struct smb2_handle *h = NULL;
310 struct smb2_create io;
311 struct smb2_lease ls;
314 smb2_util_unlink(tree, fname);
318 smb2_lease_create_share(&io, &ls, false /* dir */, fname,
319 smb2_util_share_access(test.share_mode),
321 smb2_util_lease_state(test.type));
322 io.in.durable_open = false;
323 io.in.durable_open_v2 = true;
324 io.in.persistent_open = request_persistent;
325 io.in.create_guid = GUID_random();
327 status = smb2_create(tree, mem_ctx, &io);
328 CHECK_STATUS(status, NT_STATUS_OK);
329 _h = io.out.file.handle;
331 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
332 CHECK_VAL(io.out.durable_open, false);
333 CHECK_VAL(io.out.durable_open_v2, test.durable);
334 CHECK_VAL(io.out.persistent_open, test.persistent);
335 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
336 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
337 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
338 CHECK_VAL(io.out.lease_response.lease_state,
339 smb2_util_lease_state(test.type));
342 smb2_util_close(tree, *h);
344 smb2_util_unlink(tree, fname);
345 talloc_free(mem_ctx);
350 static bool test_durable_v2_open_lease_table(struct torture_context *tctx,
351 struct smb2_tree *tree,
353 bool request_persistent,
354 struct durable_open_vs_lease *table,
360 smb2_util_unlink(tree, fname);
362 for (i = 0; i < num_tests; i++) {
363 ret = test_one_durable_v2_open_lease(tctx,
374 smb2_util_unlink(tree, fname);
379 bool test_durable_v2_open_lease(struct torture_context *tctx,
380 struct smb2_tree *tree)
386 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
387 if (!(caps & SMB2_CAP_LEASING)) {
388 torture_skip(tctx, "leases are not supported");
391 /* Choose a random name in case the state is left a little funky. */
392 snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8));
394 ret = test_durable_v2_open_lease_table(tctx, tree, fname,
395 false, /* request_persistent */
396 durable_open_vs_lease_table,
397 NUM_LEASE_OPEN_TESTS);
404 * basic test for doing a durable open
405 * and do a durable reopen on the same connection
406 * while the first open is still active (fails)
408 bool test_durable_v2_open_reopen1(struct torture_context *tctx,
409 struct smb2_tree *tree)
412 TALLOC_CTX *mem_ctx = talloc_new(tctx);
414 struct smb2_handle _h;
415 struct smb2_handle *h = NULL;
416 struct smb2_create io;
417 struct GUID create_guid = GUID_random();
420 /* Choose a random name in case the state is left a little funky. */
421 snprintf(fname, 256, "durable_v2_open_reopen1_%s.dat",
422 generate_random_str(tctx, 8));
424 smb2_util_unlink(tree, fname);
426 smb2_oplock_create_share(&io, fname,
427 smb2_util_share_access(""),
428 smb2_util_oplock_level("b"));
429 io.in.durable_open = false;
430 io.in.durable_open_v2 = true;
431 io.in.persistent_open = false;
432 io.in.create_guid = create_guid;
433 io.in.timeout = UINT32_MAX;
435 status = smb2_create(tree, mem_ctx, &io);
436 CHECK_STATUS(status, NT_STATUS_OK);
437 _h = io.out.file.handle;
439 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
440 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
441 CHECK_VAL(io.out.durable_open, false);
442 CHECK_VAL(io.out.durable_open_v2, true);
443 CHECK_VAL(io.out.persistent_open, false);
444 CHECK_VAL(io.out.timeout, io.in.timeout);
446 /* try a durable reconnect while the file is still open */
449 io.in.durable_handle_v2 = h;
450 io.in.create_guid = create_guid;
451 status = smb2_create(tree, mem_ctx, &io);
452 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
456 smb2_util_close(tree, *h);
459 smb2_util_unlink(tree, fname);
463 talloc_free(mem_ctx);
469 * basic test for doing a durable open
470 * tcp disconnect, reconnect, do a durable reopen (succeeds)
472 bool test_durable_v2_open_reopen2(struct torture_context *tctx,
473 struct smb2_tree *tree)
476 TALLOC_CTX *mem_ctx = talloc_new(tctx);
478 struct smb2_handle _h;
479 struct smb2_handle *h = NULL;
480 struct smb2_create io;
481 struct GUID create_guid = GUID_random();
482 struct GUID create_guid_invalid = GUID_random();
485 /* Choose a random name in case the state is left a little funky. */
486 snprintf(fname, 256, "durable_v2_open_reopen2_%s.dat",
487 generate_random_str(tctx, 8));
489 smb2_util_unlink(tree, fname);
491 smb2_oplock_create_share(&io, fname,
492 smb2_util_share_access(""),
493 smb2_util_oplock_level("b"));
494 io.in.durable_open = false;
495 io.in.durable_open_v2 = true;
496 io.in.persistent_open = false;
497 io.in.create_guid = create_guid;
498 io.in.timeout = UINT32_MAX;
500 status = smb2_create(tree, mem_ctx, &io);
501 CHECK_STATUS(status, NT_STATUS_OK);
502 _h = io.out.file.handle;
504 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
505 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
506 CHECK_VAL(io.out.durable_open, false);
507 CHECK_VAL(io.out.durable_open_v2, true);
508 CHECK_VAL(io.out.persistent_open, false);
509 CHECK_VAL(io.out.timeout, io.in.timeout);
511 /* disconnect, leaving the durable open */
514 if (!torture_smb2_connection(tctx, &tree)) {
515 torture_warning(tctx, "couldn't reconnect, bailing\n");
521 * first a few failure cases
526 io.in.durable_handle_v2 = h;
527 status = smb2_create(tree, mem_ctx, &io);
528 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
531 io.in.fname = "__non_existing_fname__";
532 io.in.durable_handle_v2 = h;
533 status = smb2_create(tree, mem_ctx, &io);
534 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
538 io.in.durable_handle_v2 = h;
539 status = smb2_create(tree, mem_ctx, &io);
540 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
542 /* a non-zero but non-matching create_guid does not change it: */
545 io.in.durable_handle_v2 = h;
546 io.in.create_guid = create_guid_invalid;
547 status = smb2_create(tree, mem_ctx, &io);
548 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
552 * The important difference is that the create_guid is provided.
556 io.in.durable_open_v2 = false;
557 io.in.durable_handle_v2 = h;
558 io.in.create_guid = create_guid;
561 status = smb2_create(tree, mem_ctx, &io);
562 CHECK_STATUS(status, NT_STATUS_OK);
563 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
564 CHECK_VAL(io.out.durable_open, false);
565 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
566 CHECK_VAL(io.out.persistent_open, false);
567 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
568 _h = io.out.file.handle;
571 /* disconnect one more time */
574 if (!torture_smb2_connection(tctx, &tree)) {
575 torture_warning(tctx, "couldn't reconnect, bailing\n");
581 /* These are completely ignored by the server */
582 io.in.security_flags = 0x78;
583 io.in.oplock_level = 0x78;
584 io.in.impersonation_level = 0x12345678;
585 io.in.create_flags = 0x12345678;
586 io.in.reserved = 0x12345678;
587 io.in.desired_access = 0x12345678;
588 io.in.file_attributes = 0x12345678;
589 io.in.share_access = 0x12345678;
590 io.in.create_disposition = 0x12345678;
591 io.in.create_options = 0x12345678;
592 io.in.fname = "__non_existing_fname__";
595 * only io.in.durable_handle_v2 and
596 * io.in.create_guid are checked
598 io.in.durable_open_v2 = false;
599 io.in.durable_handle_v2 = h;
600 io.in.create_guid = create_guid;
603 status = smb2_create(tree, mem_ctx, &io);
604 CHECK_STATUS(status, NT_STATUS_OK);
605 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
606 CHECK_VAL(io.out.durable_open, false);
607 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
608 CHECK_VAL(io.out.persistent_open, false);
609 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
610 _h = io.out.file.handle;
615 smb2_util_close(tree, *h);
618 smb2_util_unlink(tree, fname);
622 talloc_free(mem_ctx);
628 * lease variant of reopen2
629 * basic test for doing a durable open
630 * tcp disconnect, reconnect, do a durable reopen (succeeds)
632 bool test_durable_v2_open_reopen2_lease(struct torture_context *tctx,
633 struct smb2_tree *tree)
636 TALLOC_CTX *mem_ctx = talloc_new(tctx);
638 struct smb2_handle _h;
639 struct smb2_handle *h = NULL;
640 struct smb2_create io;
641 struct GUID create_guid = GUID_random();
642 struct smb2_lease ls;
645 struct smbcli_options options;
648 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
649 if (!(caps & SMB2_CAP_LEASING)) {
650 torture_skip(tctx, "leases are not supported");
653 options = tree->session->transport->options;
655 /* Choose a random name in case the state is left a little funky. */
656 snprintf(fname, 256, "durable_v2_open_reopen2_%s.dat",
657 generate_random_str(tctx, 8));
659 smb2_util_unlink(tree, fname);
661 lease_key = random();
662 smb2_lease_create(&io, &ls, false /* dir */, fname,
663 lease_key, smb2_util_lease_state("RWH"));
664 io.in.durable_open = false;
665 io.in.durable_open_v2 = true;
666 io.in.persistent_open = false;
667 io.in.create_guid = create_guid;
668 io.in.timeout = UINT32_MAX;
670 status = smb2_create(tree, mem_ctx, &io);
671 CHECK_STATUS(status, NT_STATUS_OK);
672 _h = io.out.file.handle;
674 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
675 CHECK_VAL(io.out.durable_open, false);
676 CHECK_VAL(io.out.durable_open_v2, true);
677 CHECK_VAL(io.out.persistent_open, false);
678 CHECK_VAL(io.out.timeout, io.in.timeout);
679 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
680 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
681 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
682 CHECK_VAL(io.out.lease_response.lease_state,
683 smb2_util_lease_state("RWH"));
684 CHECK_VAL(io.out.lease_response.lease_flags, 0);
685 CHECK_VAL(io.out.lease_response.lease_duration, 0);
687 /* disconnect, reconnect and then do durable reopen */
690 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
691 torture_warning(tctx, "couldn't reconnect, bailing\n");
696 /* a few failure tests: */
699 * several attempts without lease attached:
700 * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
701 * irrespective of file name provided
706 io.in.durable_handle_v2 = h;
707 status = smb2_create(tree, mem_ctx, &io);
708 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
711 io.in.fname = "__non_existing_fname__";
712 io.in.durable_handle_v2 = h;
713 status = smb2_create(tree, mem_ctx, &io);
714 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
718 io.in.durable_handle_v2 = h;
719 status = smb2_create(tree, mem_ctx, &io);
720 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
723 * attempt with lease provided, but
724 * with a changed lease key. => fails
728 io.in.durable_open_v2 = false;
729 io.in.durable_handle_v2 = h;
730 io.in.create_guid = create_guid;
731 io.in.lease_request = &ls;
732 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
733 /* a wrong lease key lets the request fail */
734 ls.lease_key.data[0]++;
736 status = smb2_create(tree, mem_ctx, &io);
737 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
739 /* restore the correct lease key */
740 ls.lease_key.data[0]--;
743 * this last failing attempt is almost correct:
744 * only problem is: we use the wrong filename...
745 * Note that this gives INVALID_PARAMETER.
746 * This is different from oplocks!
749 io.in.fname = "__non_existing_fname__";
750 io.in.durable_open_v2 = false;
751 io.in.durable_handle_v2 = h;
752 io.in.create_guid = create_guid;
753 io.in.lease_request = &ls;
754 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
756 status = smb2_create(tree, mem_ctx, &io);
757 CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);
760 * Now for a succeeding reconnect:
765 io.in.durable_open_v2 = false;
766 io.in.durable_handle_v2 = h;
767 io.in.create_guid = create_guid;
768 io.in.lease_request = &ls;
769 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
771 /* the requested lease state is irrelevant */
772 ls.lease_state = smb2_util_lease_state("");
776 status = smb2_create(tree, mem_ctx, &io);
777 CHECK_STATUS(status, NT_STATUS_OK);
779 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
780 CHECK_VAL(io.out.durable_open, false);
781 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
782 CHECK_VAL(io.out.persistent_open, false);
783 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
784 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
785 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
786 CHECK_VAL(io.out.lease_response.lease_state,
787 smb2_util_lease_state("RWH"));
788 CHECK_VAL(io.out.lease_response.lease_flags, 0);
789 CHECK_VAL(io.out.lease_response.lease_duration, 0);
790 _h = io.out.file.handle;
793 /* disconnect one more time */
796 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
797 torture_warning(tctx, "couldn't reconnect, bailing\n");
803 * demonstrate that various parameters are ignored
809 * These are completely ignored by the server
811 io.in.security_flags = 0x78;
812 io.in.oplock_level = 0x78;
813 io.in.impersonation_level = 0x12345678;
814 io.in.create_flags = 0x12345678;
815 io.in.reserved = 0x12345678;
816 io.in.desired_access = 0x12345678;
817 io.in.file_attributes = 0x12345678;
818 io.in.share_access = 0x12345678;
819 io.in.create_disposition = 0x12345678;
820 io.in.create_options = 0x12345678;
823 * only these are checked:
825 * - io.in.durable_handle_v2,
826 * - io.in.create_guid
827 * - io.in.lease_request->lease_key
831 io.in.durable_open_v2 = false;
832 io.in.durable_handle_v2 = h;
833 io.in.create_guid = create_guid;
834 io.in.lease_request = &ls;
836 /* the requested lease state is irrelevant */
837 ls.lease_state = smb2_util_lease_state("");
841 status = smb2_create(tree, mem_ctx, &io);
842 CHECK_STATUS(status, NT_STATUS_OK);
844 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
845 CHECK_VAL(io.out.durable_open, false);
846 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
847 CHECK_VAL(io.out.persistent_open, false);
848 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
849 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
850 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
851 CHECK_VAL(io.out.lease_response.lease_state,
852 smb2_util_lease_state("RWH"));
853 CHECK_VAL(io.out.lease_response.lease_flags, 0);
854 CHECK_VAL(io.out.lease_response.lease_duration, 0);
856 _h = io.out.file.handle;
861 smb2_util_close(tree, *h);
864 smb2_util_unlink(tree, fname);
868 talloc_free(mem_ctx);
874 * lease_v2 variant of reopen2
875 * basic test for doing a durable open
876 * tcp disconnect, reconnect, do a durable reopen (succeeds)
878 bool test_durable_v2_open_reopen2_lease_v2(struct torture_context *tctx,
879 struct smb2_tree *tree)
882 TALLOC_CTX *mem_ctx = talloc_new(tctx);
884 struct smb2_handle _h;
885 struct smb2_handle *h = NULL;
886 struct smb2_create io;
887 struct GUID create_guid = GUID_random();
888 struct smb2_lease ls;
891 struct smbcli_options options;
894 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
895 if (!(caps & SMB2_CAP_LEASING)) {
896 torture_skip(tctx, "leases are not supported");
899 options = tree->session->transport->options;
901 /* Choose a random name in case the state is left a little funky. */
902 snprintf(fname, 256, "durable_v2_open_reopen2_%s.dat",
903 generate_random_str(tctx, 8));
905 smb2_util_unlink(tree, fname);
907 lease_key = random();
908 smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
909 lease_key, 0, /* parent lease key */
910 smb2_util_lease_state("RWH"), 0 /* lease epoch */);
911 io.in.durable_open = false;
912 io.in.durable_open_v2 = true;
913 io.in.persistent_open = false;
914 io.in.create_guid = create_guid;
915 io.in.timeout = UINT32_MAX;
917 status = smb2_create(tree, mem_ctx, &io);
918 CHECK_STATUS(status, NT_STATUS_OK);
919 _h = io.out.file.handle;
921 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
922 CHECK_VAL(io.out.durable_open, false);
923 CHECK_VAL(io.out.durable_open_v2, true);
924 CHECK_VAL(io.out.persistent_open, false);
925 CHECK_VAL(io.out.timeout, io.in.timeout);
926 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
927 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
928 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
930 /* disconnect, reconnect and then do durable reopen */
933 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
934 torture_warning(tctx, "couldn't reconnect, bailing\n");
939 /* a few failure tests: */
942 * several attempts without lease attached:
943 * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
944 * irrespective of file name provided
949 io.in.durable_handle_v2 = h;
950 status = smb2_create(tree, mem_ctx, &io);
951 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
954 io.in.fname = "__non_existing_fname__";
955 io.in.durable_handle_v2 = h;
956 status = smb2_create(tree, mem_ctx, &io);
957 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
961 io.in.durable_handle_v2 = h;
962 status = smb2_create(tree, mem_ctx, &io);
963 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
966 * attempt with lease provided, but
967 * with a changed lease key. => fails
971 io.in.durable_open_v2 = false;
972 io.in.durable_handle_v2 = h;
973 io.in.create_guid = create_guid;
974 io.in.lease_request_v2 = &ls;
975 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
976 /* a wrong lease key lets the request fail */
977 ls.lease_key.data[0]++;
979 status = smb2_create(tree, mem_ctx, &io);
980 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
982 /* restore the correct lease key */
983 ls.lease_key.data[0]--;
987 * this last failing attempt is almost correct:
988 * only problem is: we use the wrong filename...
989 * Note that this gives INVALID_PARAMETER.
990 * This is different from oplocks!
993 io.in.fname = "__non_existing_fname__";
994 io.in.durable_open_v2 = false;
995 io.in.durable_handle_v2 = h;
996 io.in.create_guid = create_guid;
997 io.in.lease_request_v2 = &ls;
998 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1000 status = smb2_create(tree, mem_ctx, &io);
1001 CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);
1004 * Now for a succeeding reconnect:
1008 io.in.fname = fname;
1009 io.in.durable_open_v2 = false;
1010 io.in.durable_handle_v2 = h;
1011 io.in.create_guid = create_guid;
1012 io.in.lease_request_v2 = &ls;
1013 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1015 /* the requested lease state is irrelevant */
1016 ls.lease_state = smb2_util_lease_state("");
1020 status = smb2_create(tree, mem_ctx, &io);
1021 CHECK_STATUS(status, NT_STATUS_OK);
1023 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1024 CHECK_VAL(io.out.durable_open, false);
1025 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1026 CHECK_VAL(io.out.persistent_open, false);
1027 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1028 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
1029 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
1030 CHECK_VAL(io.out.lease_response_v2.lease_state,
1031 smb2_util_lease_state("RWH"));
1032 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
1033 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
1034 _h = io.out.file.handle;
1037 /* disconnect one more time */
1040 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
1041 torture_warning(tctx, "couldn't reconnect, bailing\n");
1047 * demonstrate that various parameters are ignored
1053 * These are completely ignored by the server
1055 io.in.security_flags = 0x78;
1056 io.in.oplock_level = 0x78;
1057 io.in.impersonation_level = 0x12345678;
1058 io.in.create_flags = 0x12345678;
1059 io.in.reserved = 0x12345678;
1060 io.in.desired_access = 0x12345678;
1061 io.in.file_attributes = 0x12345678;
1062 io.in.share_access = 0x12345678;
1063 io.in.create_disposition = 0x12345678;
1064 io.in.create_options = 0x12345678;
1065 io.in.fname = "__non_existing_fname__";
1068 * only these are checked:
1070 * - io.in.durable_handle_v2,
1071 * - io.in.create_guid
1072 * - io.in.lease_request_v2->lease_key
1075 io.in.fname = fname;
1076 io.in.durable_open_v2 = false;
1077 io.in.durable_handle_v2 = h;
1078 io.in.create_guid = create_guid;
1079 io.in.lease_request_v2 = &ls;
1081 /* the requested lease state is irrelevant */
1082 ls.lease_state = smb2_util_lease_state("");
1086 status = smb2_create(tree, mem_ctx, &io);
1087 CHECK_STATUS(status, NT_STATUS_OK);
1088 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1089 CHECK_VAL(io.out.durable_open, false);
1090 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1091 CHECK_VAL(io.out.persistent_open, false);
1092 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1093 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
1094 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
1095 CHECK_VAL(io.out.lease_response_v2.lease_state,
1096 smb2_util_lease_state("RWH"));
1097 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
1098 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
1100 _h = io.out.file.handle;
1105 smb2_util_close(tree, *h);
1108 smb2_util_unlink(tree, fname);
1112 talloc_free(mem_ctx);
1118 * Test durable request / reconnect with AppInstanceId
1120 bool test_durable_v2_open_app_instance(struct torture_context *tctx,
1121 struct smb2_tree *tree1,
1122 struct smb2_tree *tree2)
1125 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1127 struct smb2_handle _h1, _h2;
1128 struct smb2_handle *h1 = NULL, *h2 = NULL;
1129 struct smb2_create io1, io2;
1131 struct GUID create_guid_1 = GUID_random();
1132 struct GUID create_guid_2 = GUID_random();
1133 struct GUID app_instance_id = GUID_random();
1135 /* Choose a random name in case the state is left a little funky. */
1136 snprintf(fname, 256, "durable_v2_open_app_instance_%s.dat",
1137 generate_random_str(tctx, 8));
1139 smb2_util_unlink(tree1, fname);
1141 ZERO_STRUCT(break_info);
1142 tree1->session->transport->oplock.handler = torture_oplock_handler;
1143 tree1->session->transport->oplock.private_data = tree1;
1145 smb2_oplock_create_share(&io1, fname,
1146 smb2_util_share_access(""),
1147 smb2_util_oplock_level("b"));
1148 io1.in.durable_open = false;
1149 io1.in.durable_open_v2 = true;
1150 io1.in.persistent_open = false;
1151 io1.in.create_guid = create_guid_1;
1152 io1.in.app_instance_id = &app_instance_id;
1153 io1.in.timeout = UINT32_MAX;
1155 status = smb2_create(tree1, mem_ctx, &io1);
1156 CHECK_STATUS(status, NT_STATUS_OK);
1157 _h1 = io1.out.file.handle;
1159 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1160 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1161 CHECK_VAL(io1.out.durable_open, false);
1162 CHECK_VAL(io1.out.durable_open_v2, true);
1163 CHECK_VAL(io1.out.persistent_open, false);
1164 CHECK_VAL(io1.out.timeout, io1.in.timeout);
1167 * try to open the file as durable from a second tree with
1168 * a different create guid but the same app_instance_id
1169 * while the first handle is still open.
1172 smb2_oplock_create_share(&io2, fname,
1173 smb2_util_share_access(""),
1174 smb2_util_oplock_level("b"));
1175 io2.in.durable_open = false;
1176 io2.in.durable_open_v2 = true;
1177 io2.in.persistent_open = false;
1178 io2.in.create_guid = create_guid_2;
1179 io2.in.app_instance_id = &app_instance_id;
1180 io2.in.timeout = UINT32_MAX;
1182 status = smb2_create(tree2, mem_ctx, &io2);
1183 CHECK_STATUS(status, NT_STATUS_OK);
1184 _h2 = io2.out.file.handle;
1186 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1187 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
1188 CHECK_VAL(io2.out.durable_open, false);
1189 CHECK_VAL(io2.out.durable_open_v2, true);
1190 CHECK_VAL(io2.out.persistent_open, false);
1191 CHECK_VAL(io2.out.timeout, io2.in.timeout);
1193 CHECK_VAL(break_info.count, 0);
1195 status = smb2_util_close(tree1, *h1);
1196 CHECK_STATUS(status, NT_STATUS_FILE_CLOSED);
1201 smb2_util_close(tree1, *h1);
1204 smb2_util_close(tree2, *h2);
1207 smb2_util_unlink(tree2, fname);
1212 talloc_free(mem_ctx);
1219 * basic persistent open test.
1221 * This test tests durable open with all possible oplock types.
1224 struct durable_open_vs_oplock persistent_open_oplock_ca_table[NUM_OPLOCK_OPEN_TESTS] =
1226 { "", "", true, true },
1227 { "", "R", true, true },
1228 { "", "W", true, true },
1229 { "", "D", true, true },
1230 { "", "RD", true, true },
1231 { "", "RW", true, true },
1232 { "", "WD", true, true },
1233 { "", "RWD", true, true },
1235 { "s", "", true, true },
1236 { "s", "R", true, true },
1237 { "s", "W", true, true },
1238 { "s", "D", true, true },
1239 { "s", "RD", true, true },
1240 { "s", "RW", true, true },
1241 { "s", "WD", true, true },
1242 { "s", "RWD", true, true },
1244 { "x", "", true, true },
1245 { "x", "R", true, true },
1246 { "x", "W", true, true },
1247 { "x", "D", true, true },
1248 { "x", "RD", true, true },
1249 { "x", "RW", true, true },
1250 { "x", "WD", true, true },
1251 { "x", "RWD", true, true },
1253 { "b", "", true, true },
1254 { "b", "R", true, true },
1255 { "b", "W", true, true },
1256 { "b", "D", true, true },
1257 { "b", "RD", true, true },
1258 { "b", "RW", true, true },
1259 { "b", "WD", true, true },
1260 { "b", "RWD", true, true },
1263 bool test_persistent_open_oplock(struct torture_context *tctx,
1264 struct smb2_tree *tree)
1268 uint32_t share_capabilities;
1269 bool share_is_ca = false;
1270 struct durable_open_vs_oplock *table;
1272 /* Choose a random name in case the state is left a little funky. */
1273 snprintf(fname, 256, "persistent_open_oplock_%s.dat", generate_random_str(tctx, 8));
1275 share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
1276 share_is_ca = share_capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY;
1279 table = persistent_open_oplock_ca_table;
1281 table = durable_open_vs_oplock_table;
1284 ret = test_durable_v2_open_oplock_table(tctx, tree, fname,
1285 true, /* request_persistent */
1287 NUM_OPLOCK_OPEN_TESTS);
1295 * basic persistent handle open test.
1296 * persistent state should only be granted when requested
1297 * along with a batch oplock or a handle lease.
1299 * This test tests persistent open with all valid lease types.
1302 struct durable_open_vs_lease persistent_open_lease_ca_table[NUM_LEASE_OPEN_TESTS] =
1304 { "", "", true, true },
1305 { "", "R", true, true },
1306 { "", "W", true, true },
1307 { "", "D", true, true },
1308 { "", "RW", true, true },
1309 { "", "RD", true, true },
1310 { "", "WD", true, true },
1311 { "", "RWD", true, true },
1313 { "R", "", true, true },
1314 { "R", "R", true, true },
1315 { "R", "W", true, true },
1316 { "R", "D", true, true },
1317 { "R", "RW", true, true },
1318 { "R", "RD", true, true },
1319 { "R", "DW", true, true },
1320 { "R", "RWD", true, true },
1322 { "RW", "", true, true },
1323 { "RW", "R", true, true },
1324 { "RW", "W", true, true },
1325 { "RW", "D", true, true },
1326 { "RW", "RW", true, true },
1327 { "RW", "RD", true, true },
1328 { "RW", "WD", true, true },
1329 { "RW", "RWD", true, true },
1331 { "RH", "", true, true },
1332 { "RH", "R", true, true },
1333 { "RH", "W", true, true },
1334 { "RH", "D", true, true },
1335 { "RH", "RW", true, true },
1336 { "RH", "RD", true, true },
1337 { "RH", "WD", true, true },
1338 { "RH", "RWD", true, true },
1340 { "RHW", "", true, true },
1341 { "RHW", "R", true, true },
1342 { "RHW", "W", true, true },
1343 { "RHW", "D", true, true },
1344 { "RHW", "RW", true, true },
1345 { "RHW", "RD", true, true },
1346 { "RHW", "WD", true, true },
1347 { "RHW", "RWD", true, true },
1350 bool test_persistent_open_lease(struct torture_context *tctx,
1351 struct smb2_tree *tree)
1356 uint32_t share_capabilities;
1358 struct durable_open_vs_lease *table;
1360 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
1361 if (!(caps & SMB2_CAP_LEASING)) {
1362 torture_skip(tctx, "leases are not supported");
1365 /* Choose a random name in case the state is left a little funky. */
1366 snprintf(fname, 256, "persistent_open_lease_%s.dat", generate_random_str(tctx, 8));
1368 share_capabilities = smb2cli_tcon_capabilities(tree->smbXcli);
1369 share_is_ca = share_capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY;
1372 table = persistent_open_lease_ca_table;
1374 table = durable_open_vs_lease_table;
1377 ret = test_durable_v2_open_lease_table(tctx, tree, fname,
1378 true, /* request_persistent */
1380 NUM_LEASE_OPEN_TESTS);
1387 struct torture_suite *torture_smb2_durable_v2_open_init(void)
1389 struct torture_suite *suite =
1390 torture_suite_create(talloc_autofree_context(), "durable-v2-open");
1392 torture_suite_add_1smb2_test(suite, "open-oplock", test_durable_v2_open_oplock);
1393 torture_suite_add_1smb2_test(suite, "open-lease", test_durable_v2_open_lease);
1394 torture_suite_add_1smb2_test(suite, "reopen1", test_durable_v2_open_reopen1);
1395 torture_suite_add_1smb2_test(suite, "reopen2", test_durable_v2_open_reopen2);
1396 torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_v2_open_reopen2_lease);
1397 torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_v2_open_reopen2_lease_v2);
1398 torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance);
1399 torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock);
1400 torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease);
1402 suite->description = talloc_strdup(suite, "SMB2-DURABLE-V2-OPEN tests");