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); \
55 * basic durable_open test.
56 * durable state should only be granted when requested
57 * along with a batch oplock or a handle lease.
59 * This test tests durable open with all possible oplock types.
62 struct durable_open_vs_oplock
{
64 const char *share_mode
;
69 #define NUM_OPLOCK_TYPES 4
70 #define NUM_SHARE_MODES 8
71 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
72 static struct durable_open_vs_oplock durable_open_vs_oplock_table
[NUM_OPLOCK_OPEN_TESTS
] =
74 { "", "", false, false },
75 { "", "R", false, false },
76 { "", "W", false, false },
77 { "", "D", false, false },
78 { "", "RD", false, false },
79 { "", "RW", false, false },
80 { "", "WD", false, false },
81 { "", "RWD", false, false },
83 { "s", "", false, false },
84 { "s", "R", false, false },
85 { "s", "W", false, false },
86 { "s", "D", false, false },
87 { "s", "RD", false, false },
88 { "s", "RW", false, false },
89 { "s", "WD", false, false },
90 { "s", "RWD", false, false },
92 { "x", "", false, false },
93 { "x", "R", false, false },
94 { "x", "W", false, false },
95 { "x", "D", false, false },
96 { "x", "RD", false, false },
97 { "x", "RW", false, false },
98 { "x", "WD", false, false },
99 { "x", "RWD", false, false },
101 { "b", "", true, false },
102 { "b", "R", true, false },
103 { "b", "W", true, false },
104 { "b", "D", true, false },
105 { "b", "RD", true, false },
106 { "b", "RW", true, false },
107 { "b", "WD", true, false },
108 { "b", "RWD", true, false },
111 static bool test_one_durable_v2_open_oplock(struct torture_context
*tctx
,
112 struct smb2_tree
*tree
,
114 bool request_persistent
,
115 struct durable_open_vs_oplock test
)
118 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
119 struct smb2_handle _h
;
120 struct smb2_handle
*h
= NULL
;
122 struct smb2_create io
;
124 smb2_util_unlink(tree
, fname
);
126 smb2_oplock_create_share(&io
, fname
,
127 smb2_util_share_access(test
.share_mode
),
128 smb2_util_oplock_level(test
.level
));
129 io
.in
.durable_open
= false;
130 io
.in
.durable_open_v2
= true;
131 io
.in
.persistent_open
= request_persistent
;
132 io
.in
.create_guid
= GUID_random();
134 status
= smb2_create(tree
, mem_ctx
, &io
);
135 CHECK_STATUS(status
, NT_STATUS_OK
);
136 _h
= io
.out
.file
.handle
;
138 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
139 CHECK_VAL(io
.out
.durable_open
, false);
140 CHECK_VAL(io
.out
.durable_open_v2
, test
.durable
);
141 CHECK_VAL(io
.out
.persistent_open
, test
.persistent
);
142 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level(test
.level
));
146 smb2_util_close(tree
, *h
);
148 smb2_util_unlink(tree
, fname
);
149 talloc_free(mem_ctx
);
154 static bool test_durable_v2_open_oplock_table(struct torture_context
*tctx
,
155 struct smb2_tree
*tree
,
157 bool request_persistent
,
158 struct durable_open_vs_oplock
*table
,
164 smb2_util_unlink(tree
, fname
);
166 for (i
= 0; i
< num_tests
; i
++) {
167 ret
= test_one_durable_v2_open_oplock(tctx
,
178 smb2_util_unlink(tree
, fname
);
183 bool test_durable_v2_open_oplock(struct torture_context
*tctx
,
184 struct smb2_tree
*tree
)
189 /* Choose a random name in case the state is left a little funky. */
190 snprintf(fname
, 256, "durable_open_oplock_%s.dat",
191 generate_random_str(tctx
, 8));
193 ret
= test_durable_v2_open_oplock_table(tctx
, tree
, fname
,
194 false, /* request_persistent */
195 durable_open_vs_oplock_table
,
196 NUM_OPLOCK_OPEN_TESTS
);
204 * basic durable handle open test.
205 * persistent state should only be granted when requested
206 * along with a batch oplock or a handle lease.
208 * This test tests persistent open with all valid lease types.
211 struct durable_open_vs_lease
{
213 const char *share_mode
;
218 #define NUM_LEASE_TYPES 5
219 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
220 static struct durable_open_vs_lease durable_open_vs_lease_table
[NUM_LEASE_OPEN_TESTS
] =
222 { "", "", false, false },
223 { "", "R", false, false },
224 { "", "W", false, false },
225 { "", "D", false, false },
226 { "", "RW", false, false },
227 { "", "RD", false, false },
228 { "", "WD", false, false },
229 { "", "RWD", false, false },
231 { "R", "", false, false },
232 { "R", "R", false, false },
233 { "R", "W", false, false },
234 { "R", "D", false, false },
235 { "R", "RW", false, false },
236 { "R", "RD", false, false },
237 { "R", "DW", false, false },
238 { "R", "RWD", false, false },
240 { "RW", "", false, false },
241 { "RW", "R", false, false },
242 { "RW", "W", false, false },
243 { "RW", "D", false, false },
244 { "RW", "RW", false, false },
245 { "RW", "RD", false, false },
246 { "RW", "WD", false, false },
247 { "RW", "RWD", false, false },
249 { "RH", "", true, false },
250 { "RH", "R", true, false },
251 { "RH", "W", true, false },
252 { "RH", "D", true, false },
253 { "RH", "RW", true, false },
254 { "RH", "RD", true, false },
255 { "RH", "WD", true, false },
256 { "RH", "RWD", true, false },
258 { "RHW", "", true, false },
259 { "RHW", "R", true, false },
260 { "RHW", "W", true, false },
261 { "RHW", "D", true, false },
262 { "RHW", "RW", true, false },
263 { "RHW", "RD", true, false },
264 { "RHW", "WD", true, false },
265 { "RHW", "RWD", true, false },
268 static bool test_one_durable_v2_open_lease(struct torture_context
*tctx
,
269 struct smb2_tree
*tree
,
271 bool request_persistent
,
272 struct durable_open_vs_lease test
)
275 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
276 struct smb2_handle _h
;
277 struct smb2_handle
*h
= NULL
;
279 struct smb2_create io
;
280 struct smb2_lease ls
;
283 smb2_util_unlink(tree
, fname
);
287 smb2_lease_create_share(&io
, &ls
, false /* dir */, fname
,
288 smb2_util_share_access(test
.share_mode
),
290 smb2_util_lease_state(test
.type
));
291 io
.in
.durable_open
= false;
292 io
.in
.durable_open_v2
= true;
293 io
.in
.persistent_open
= request_persistent
;
294 io
.in
.create_guid
= GUID_random();
296 status
= smb2_create(tree
, mem_ctx
, &io
);
297 CHECK_STATUS(status
, NT_STATUS_OK
);
298 _h
= io
.out
.file
.handle
;
300 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
301 CHECK_VAL(io
.out
.durable_open
, false);
302 CHECK_VAL(io
.out
.durable_open_v2
, test
.durable
);
303 CHECK_VAL(io
.out
.persistent_open
, test
.persistent
);
304 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
305 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease
);
306 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease
);
307 CHECK_VAL(io
.out
.lease_response
.lease_state
,
308 smb2_util_lease_state(test
.type
));
311 smb2_util_close(tree
, *h
);
313 smb2_util_unlink(tree
, fname
);
314 talloc_free(mem_ctx
);
319 static bool test_durable_v2_open_lease_table(struct torture_context
*tctx
,
320 struct smb2_tree
*tree
,
322 bool request_persistent
,
323 struct durable_open_vs_lease
*table
,
329 smb2_util_unlink(tree
, fname
);
331 for (i
= 0; i
< num_tests
; i
++) {
332 ret
= test_one_durable_v2_open_lease(tctx
,
343 smb2_util_unlink(tree
, fname
);
348 bool test_durable_v2_open_lease(struct torture_context
*tctx
,
349 struct smb2_tree
*tree
)
355 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
356 if (!(caps
& SMB2_CAP_LEASING
)) {
357 torture_skip(tctx
, "leases are not supported");
360 /* Choose a random name in case the state is left a little funky. */
361 snprintf(fname
, 256, "durable_open_lease_%s.dat", generate_random_str(tctx
, 8));
363 ret
= test_durable_v2_open_lease_table(tctx
, tree
, fname
,
364 false, /* request_persistent */
365 durable_open_vs_lease_table
,
366 NUM_LEASE_OPEN_TESTS
);
373 * basic test for doing a durable open
374 * and do a durable reopen on the same connection
375 * while the first open is still active (fails)
377 bool test_durable_v2_open_reopen1(struct torture_context
*tctx
,
378 struct smb2_tree
*tree
)
381 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
383 struct smb2_handle _h
;
384 struct smb2_handle
*h
= NULL
;
385 struct smb2_create io
;
386 struct GUID create_guid
= GUID_random();
389 /* Choose a random name in case the state is left a little funky. */
390 snprintf(fname
, 256, "durable_v2_open_reopen1_%s.dat",
391 generate_random_str(tctx
, 8));
393 smb2_util_unlink(tree
, fname
);
395 smb2_oplock_create_share(&io
, fname
,
396 smb2_util_share_access(""),
397 smb2_util_oplock_level("b"));
398 io
.in
.durable_open
= false;
399 io
.in
.durable_open_v2
= true;
400 io
.in
.persistent_open
= false;
401 io
.in
.create_guid
= create_guid
;
402 io
.in
.timeout
= UINT32_MAX
;
404 status
= smb2_create(tree
, mem_ctx
, &io
);
405 CHECK_STATUS(status
, NT_STATUS_OK
);
406 _h
= io
.out
.file
.handle
;
408 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
409 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
410 CHECK_VAL(io
.out
.durable_open
, false);
411 CHECK_VAL(io
.out
.durable_open_v2
, true);
412 CHECK_VAL(io
.out
.persistent_open
, false);
413 CHECK_VAL(io
.out
.timeout
, io
.in
.timeout
);
415 /* try a durable reconnect while the file is still open */
418 io
.in
.durable_handle_v2
= h
;
419 io
.in
.create_guid
= create_guid
;
420 status
= smb2_create(tree
, mem_ctx
, &io
);
421 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
425 smb2_util_close(tree
, *h
);
428 smb2_util_unlink(tree
, fname
);
432 talloc_free(mem_ctx
);
438 * basic test for doing a durable open
439 * tcp disconnect, reconnect, do a durable reopen (succeeds)
441 bool test_durable_v2_open_reopen2(struct torture_context
*tctx
,
442 struct smb2_tree
*tree
)
445 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
447 struct smb2_handle _h
;
448 struct smb2_handle
*h
= NULL
;
449 struct smb2_create io1
, io2
;
452 /* Choose a random name in case the state is left a little funky. */
453 snprintf(fname
, 256, "durable_v2_open_reopen2_%s.dat",
454 generate_random_str(tctx
, 8));
456 smb2_util_unlink(tree
, fname
);
458 smb2_oplock_create_share(&io1
, fname
,
459 smb2_util_share_access(""),
460 smb2_util_oplock_level("b"));
461 io1
.in
.durable_open
= false;
462 io1
.in
.durable_open_v2
= true;
463 io1
.in
.persistent_open
= false;
464 io1
.in
.create_guid
= GUID_random();
465 io1
.in
.timeout
= UINT32_MAX
;
467 status
= smb2_create(tree
, mem_ctx
, &io1
);
468 CHECK_STATUS(status
, NT_STATUS_OK
);
469 _h
= io1
.out
.file
.handle
;
471 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
472 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
473 CHECK_VAL(io1
.out
.durable_open
, false);
474 CHECK_VAL(io1
.out
.durable_open_v2
, true);
475 CHECK_VAL(io1
.out
.persistent_open
, false);
476 CHECK_VAL(io1
.out
.timeout
, io1
.in
.timeout
);
478 /* disconnect, reconnect and then do durable reopen */
482 if (!torture_smb2_connection(tctx
, &tree
)) {
483 torture_warning(tctx
, "couldn't reconnect, bailing\n");
490 io2
.in
.durable_handle_v2
= h
;
491 status
= smb2_create(tree
, mem_ctx
, &io2
);
492 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
495 io2
.in
.fname
= "__non_existing_fname__";
496 io2
.in
.durable_handle_v2
= h
;
497 status
= smb2_create(tree
, mem_ctx
, &io2
);
498 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
501 io2
.in
.fname
= fname
;
502 io2
.in
.durable_handle_v2
= h
;
503 status
= smb2_create(tree
, mem_ctx
, &io2
);
504 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
508 * These are completely ignored by the server
510 io2
.in
.security_flags
= 0x78;
511 io2
.in
.oplock_level
= 0x78;
512 io2
.in
.impersonation_level
= 0x12345678;
513 io2
.in
.create_flags
= 0x12345678;
514 io2
.in
.reserved
= 0x12345678;
515 io2
.in
.desired_access
= 0x12345678;
516 io2
.in
.file_attributes
= 0x12345678;
517 io2
.in
.share_access
= 0x12345678;
518 io2
.in
.create_disposition
= 0x12345678;
519 io2
.in
.create_options
= 0x12345678;
520 io2
.in
.fname
= "__non_existing_fname__";
523 * only io2.in.durable_handle_v2 and
524 * io2.in.create_guid are checked
526 io2
.in
.durable_open_v2
= false;
527 io2
.in
.durable_handle_v2
= h
;
528 io2
.in
.create_guid
= io1
.in
.create_guid
;
531 status
= smb2_create(tree
, mem_ctx
, &io2
);
532 CHECK_STATUS(status
, NT_STATUS_OK
);
533 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
534 CHECK_VAL(io2
.out
.durable_open
, false);
535 CHECK_VAL(io2
.out
.durable_open_v2
, true);
536 CHECK_VAL(io2
.out
.persistent_open
, false);
537 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
538 _h
= io2
.out
.file
.handle
;
543 smb2_util_close(tree
, *h
);
546 smb2_util_unlink(tree
, fname
);
550 talloc_free(mem_ctx
);
556 * basic persistent open test.
558 * This test tests durable open with all possible oplock types.
561 struct durable_open_vs_oplock persistent_open_oplock_ca_table
[NUM_OPLOCK_OPEN_TESTS
] =
563 { "", "", true, true },
564 { "", "R", true, true },
565 { "", "W", true, true },
566 { "", "D", true, true },
567 { "", "RD", true, true },
568 { "", "RW", true, true },
569 { "", "WD", true, true },
570 { "", "RWD", true, true },
572 { "s", "", true, true },
573 { "s", "R", true, true },
574 { "s", "W", true, true },
575 { "s", "D", true, true },
576 { "s", "RD", true, true },
577 { "s", "RW", true, true },
578 { "s", "WD", true, true },
579 { "s", "RWD", true, true },
581 { "x", "", true, true },
582 { "x", "R", true, true },
583 { "x", "W", true, true },
584 { "x", "D", true, true },
585 { "x", "RD", true, true },
586 { "x", "RW", true, true },
587 { "x", "WD", true, true },
588 { "x", "RWD", true, true },
590 { "b", "", true, true },
591 { "b", "R", true, true },
592 { "b", "W", true, true },
593 { "b", "D", true, true },
594 { "b", "RD", true, true },
595 { "b", "RW", true, true },
596 { "b", "WD", true, true },
597 { "b", "RWD", true, true },
600 bool test_persistent_open_oplock(struct torture_context
*tctx
,
601 struct smb2_tree
*tree
)
605 uint32_t share_capabilities
;
606 bool share_is_ca
= false;
607 struct durable_open_vs_oplock
*table
;
609 /* Choose a random name in case the state is left a little funky. */
610 snprintf(fname
, 256, "persistent_open_oplock_%s.dat", generate_random_str(tctx
, 8));
612 share_capabilities
= smb2cli_tcon_capabilities(tree
->smbXcli
);
613 share_is_ca
= share_capabilities
& SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY
;
616 table
= persistent_open_oplock_ca_table
;
618 table
= durable_open_vs_oplock_table
;
621 ret
= test_durable_v2_open_oplock_table(tctx
, tree
, fname
,
622 true, /* request_persistent */
624 NUM_OPLOCK_OPEN_TESTS
);
632 * basic persistent handle open test.
633 * persistent state should only be granted when requested
634 * along with a batch oplock or a handle lease.
636 * This test tests persistent open with all valid lease types.
639 struct durable_open_vs_lease persistent_open_lease_ca_table
[NUM_LEASE_OPEN_TESTS
] =
641 { "", "", true, true },
642 { "", "R", true, true },
643 { "", "W", true, true },
644 { "", "D", true, true },
645 { "", "RW", true, true },
646 { "", "RD", true, true },
647 { "", "WD", true, true },
648 { "", "RWD", true, true },
650 { "R", "", true, true },
651 { "R", "R", true, true },
652 { "R", "W", true, true },
653 { "R", "D", true, true },
654 { "R", "RW", true, true },
655 { "R", "RD", true, true },
656 { "R", "DW", true, true },
657 { "R", "RWD", true, true },
659 { "RW", "", true, true },
660 { "RW", "R", true, true },
661 { "RW", "W", true, true },
662 { "RW", "D", true, true },
663 { "RW", "RW", true, true },
664 { "RW", "RD", true, true },
665 { "RW", "WD", true, true },
666 { "RW", "RWD", true, true },
668 { "RH", "", true, true },
669 { "RH", "R", true, true },
670 { "RH", "W", true, true },
671 { "RH", "D", true, true },
672 { "RH", "RW", true, true },
673 { "RH", "RD", true, true },
674 { "RH", "WD", true, true },
675 { "RH", "RWD", true, true },
677 { "RHW", "", true, true },
678 { "RHW", "R", true, true },
679 { "RHW", "W", true, true },
680 { "RHW", "D", true, true },
681 { "RHW", "RW", true, true },
682 { "RHW", "RD", true, true },
683 { "RHW", "WD", true, true },
684 { "RHW", "RWD", true, true },
687 bool test_persistent_open_lease(struct torture_context
*tctx
,
688 struct smb2_tree
*tree
)
693 uint32_t share_capabilities
;
695 struct durable_open_vs_lease
*table
;
697 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
698 if (!(caps
& SMB2_CAP_LEASING
)) {
699 torture_skip(tctx
, "leases are not supported");
702 /* Choose a random name in case the state is left a little funky. */
703 snprintf(fname
, 256, "persistent_open_lease_%s.dat", generate_random_str(tctx
, 8));
705 share_capabilities
= smb2cli_tcon_capabilities(tree
->smbXcli
);
706 share_is_ca
= share_capabilities
& SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY
;
709 table
= persistent_open_lease_ca_table
;
711 table
= durable_open_vs_lease_table
;
714 ret
= test_durable_v2_open_lease_table(tctx
, tree
, fname
,
715 true, /* request_persistent */
717 NUM_LEASE_OPEN_TESTS
);
724 struct torture_suite
*torture_smb2_durable_v2_open_init(void)
726 struct torture_suite
*suite
=
727 torture_suite_create(talloc_autofree_context(), "durable-v2-open");
729 torture_suite_add_1smb2_test(suite
, "open-oplock", test_durable_v2_open_oplock
);
730 torture_suite_add_1smb2_test(suite
, "open-lease", test_durable_v2_open_lease
);
731 torture_suite_add_1smb2_test(suite
, "reopen1", test_durable_v2_open_reopen1
);
732 torture_suite_add_1smb2_test(suite
, "reopen2", test_durable_v2_open_reopen2
);
733 torture_suite_add_1smb2_test(suite
, "persistent-open-oplock", test_persistent_open_oplock
);
734 torture_suite_add_1smb2_test(suite
, "persistent-open-lease", test_persistent_open_lease
);
736 suite
->description
= talloc_strdup(suite
, "SMB2-DURABLE-V2-OPEN tests");