2 Unix SMB/CIFS implementation.
4 test suite for SMB2 durable opens
6 Copyright (C) Stefan Metzmacher 2008
7 Copyright (C) Michael Adam 2011-2012
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include "libcli/smb2/smb2.h"
25 #include "libcli/smb2/smb2_calls.h"
26 #include "../libcli/smb/smbXcli_base.h"
27 #include "torture/torture.h"
28 #include "torture/smb2/proto.h"
29 #include "../libcli/smb/smbXcli_base.h"
31 #define CHECK_VAL(v, correct) do { \
32 if ((v) != (correct)) { \
33 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should be 0x%llx\n", \
34 __location__, #v, (unsigned long long)v, (unsigned long long)correct); \
38 #define CHECK_NOT_VAL(v, incorrect) do { \
39 if ((v) == (incorrect)) { \
40 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should not be 0x%llx\n", \
41 __location__, #v, (unsigned long long)v, (unsigned long long)incorrect); \
45 #define CHECK_NOT_NULL(p) do { \
47 torture_result(tctx, TORTURE_FAIL, "(%s): %s is NULL but it should not be.\n", \
52 #define CHECK_STATUS(status, correct) do { \
53 if (!NT_STATUS_EQUAL(status, correct)) { \
54 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
55 nt_errstr(status), nt_errstr(correct)); \
60 #define CHECK_CREATED(__io, __created, __attribute) \
62 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
63 CHECK_VAL((__io)->out.size, 0); \
64 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
65 CHECK_VAL((__io)->out.reserved2, 0); \
68 #define CHECK_CREATED_SIZE(__io, __created, __attribute, __alloc_size, __size) \
70 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
71 CHECK_VAL((__io)->out.alloc_size, (__alloc_size)); \
72 CHECK_VAL((__io)->out.size, (__size)); \
73 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
74 CHECK_VAL((__io)->out.reserved2, 0); \
80 * basic durable_open test.
81 * durable state should only be granted when requested
82 * along with a batch oplock or a handle lease.
84 * This test tests durable open with all possible oplock types.
87 struct durable_open_vs_oplock
{
89 const char *share_mode
;
93 #define NUM_OPLOCK_TYPES 4
94 #define NUM_SHARE_MODES 8
95 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
96 static struct durable_open_vs_oplock durable_open_vs_oplock_table
[NUM_OPLOCK_OPEN_TESTS
] =
105 { "", "RWD", false },
111 { "s", "RD", false },
112 { "s", "RW", false },
113 { "s", "WD", false },
114 { "s", "RWD", false },
120 { "x", "RD", false },
121 { "x", "RW", false },
122 { "x", "WD", false },
123 { "x", "RWD", false },
132 { "b", "RWD", true },
135 static bool test_one_durable_open_open_oplock(struct torture_context
*tctx
,
136 struct smb2_tree
*tree
,
138 struct durable_open_vs_oplock test
)
141 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
142 struct smb2_handle _h
;
143 struct smb2_handle
*h
= NULL
;
145 struct smb2_create io
;
147 smb2_util_unlink(tree
, fname
);
149 smb2_oplock_create_share(&io
, fname
,
150 smb2_util_share_access(test
.share_mode
),
151 smb2_util_oplock_level(test
.level
));
152 io
.in
.durable_open
= true;
154 status
= smb2_create(tree
, mem_ctx
, &io
);
155 CHECK_STATUS(status
, NT_STATUS_OK
);
156 _h
= io
.out
.file
.handle
;
158 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
159 CHECK_VAL(io
.out
.durable_open
, test
.expected
);
160 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level(test
.level
));
164 smb2_util_close(tree
, *h
);
166 smb2_util_unlink(tree
, fname
);
167 talloc_free(mem_ctx
);
172 static bool test_durable_open_open_oplock(struct torture_context
*tctx
,
173 struct smb2_tree
*tree
)
175 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
180 /* Choose a random name in case the state is left a little funky. */
181 snprintf(fname
, 256, "durable_open_open_oplock_%s.dat", generate_random_str(tctx
, 8));
183 smb2_util_unlink(tree
, fname
);
185 /* test various oplock levels with durable open */
187 for (i
= 0; i
< NUM_OPLOCK_OPEN_TESTS
; i
++) {
188 ret
= test_one_durable_open_open_oplock(tctx
,
191 durable_open_vs_oplock_table
[i
]);
198 smb2_util_unlink(tree
, fname
);
200 talloc_free(mem_ctx
);
206 * basic durable_open test.
207 * durable state should only be granted when requested
208 * along with a batch oplock or a handle lease.
210 * This test tests durable open with all valid lease types.
213 struct durable_open_vs_lease
{
215 const char *share_mode
;
219 #define NUM_LEASE_TYPES 5
220 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
221 static struct durable_open_vs_lease durable_open_vs_lease_table
[NUM_LEASE_OPEN_TESTS
] =
230 { "", "RWD", false },
236 { "R", "RW", false },
237 { "R", "RD", false },
238 { "R", "DW", false },
239 { "R", "RWD", false },
242 { "RW", "R", false },
243 { "RW", "W", false },
244 { "RW", "D", false },
245 { "RW", "RW", false },
246 { "RW", "RD", false },
247 { "RW", "WD", false },
248 { "RW", "RWD", false },
254 { "RH", "RW", true },
255 { "RH", "RD", true },
256 { "RH", "WD", true },
257 { "RH", "RWD", true },
260 { "RHW", "R", true },
261 { "RHW", "W", true },
262 { "RHW", "D", true },
263 { "RHW", "RW", true },
264 { "RHW", "RD", true },
265 { "RHW", "WD", true },
266 { "RHW", "RWD", true },
269 static bool test_one_durable_open_open_lease(struct torture_context
*tctx
,
270 struct smb2_tree
*tree
,
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
;
284 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
285 if (!(caps
& SMB2_CAP_LEASING
)) {
286 torture_skip(tctx
, "leases are not supported");
289 smb2_util_unlink(tree
, fname
);
293 smb2_lease_create_share(&io
, &ls
, false /* dir */, fname
,
294 smb2_util_share_access(test
.share_mode
),
296 smb2_util_lease_state(test
.type
));
297 io
.in
.durable_open
= true;
299 status
= smb2_create(tree
, mem_ctx
, &io
);
300 CHECK_STATUS(status
, NT_STATUS_OK
);
301 _h
= io
.out
.file
.handle
;
303 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
304 CHECK_VAL(io
.out
.durable_open
, test
.expected
);
305 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
306 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease
);
307 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease
);
308 CHECK_VAL(io
.out
.lease_response
.lease_state
,
309 smb2_util_lease_state(test
.type
));
312 smb2_util_close(tree
, *h
);
314 smb2_util_unlink(tree
, fname
);
315 talloc_free(mem_ctx
);
320 static bool test_durable_open_open_lease(struct torture_context
*tctx
,
321 struct smb2_tree
*tree
)
323 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
329 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
330 if (!(caps
& SMB2_CAP_LEASING
)) {
331 torture_skip(tctx
, "leases are not supported");
334 /* Choose a random name in case the state is left a little funky. */
335 snprintf(fname
, 256, "durable_open_open_lease_%s.dat", generate_random_str(tctx
, 8));
337 smb2_util_unlink(tree
, fname
);
340 /* test various oplock levels with durable open */
342 for (i
= 0; i
< NUM_LEASE_OPEN_TESTS
; i
++) {
343 ret
= test_one_durable_open_open_lease(tctx
,
346 durable_open_vs_lease_table
[i
]);
353 smb2_util_unlink(tree
, fname
);
355 talloc_free(mem_ctx
);
361 * basic test for doing a durable open
362 * and do a durable reopen on the same connection
363 * while the first open is still active (fails)
365 static bool test_durable_open_reopen1(struct torture_context
*tctx
,
366 struct smb2_tree
*tree
)
369 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
371 struct smb2_handle _h
;
372 struct smb2_handle
*h
= NULL
;
373 struct smb2_create io1
, io2
;
376 /* Choose a random name in case the state is left a little funky. */
377 snprintf(fname
, 256, "durable_open_reopen1_%s.dat",
378 generate_random_str(tctx
, 8));
380 smb2_util_unlink(tree
, fname
);
382 smb2_oplock_create_share(&io1
, fname
,
383 smb2_util_share_access(""),
384 smb2_util_oplock_level("b"));
385 io1
.in
.durable_open
= true;
387 status
= smb2_create(tree
, mem_ctx
, &io1
);
388 CHECK_STATUS(status
, NT_STATUS_OK
);
389 _h
= io1
.out
.file
.handle
;
391 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
392 CHECK_VAL(io1
.out
.durable_open
, true);
393 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
395 /* try a durable reconnect while the file is still open */
397 io2
.in
.fname
= fname
;
398 io2
.in
.durable_handle
= h
;
400 status
= smb2_create(tree
, mem_ctx
, &io2
);
401 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
405 smb2_util_close(tree
, *h
);
408 smb2_util_unlink(tree
, fname
);
412 talloc_free(mem_ctx
);
418 * Basic test for doing a durable open
419 * and do a session reconnect while the first
420 * session is still active and the handle is
421 * still open in the client.
422 * This closes the original session and a
423 * durable reconnect on the new session succeeds.
425 static bool test_durable_open_reopen1a(struct torture_context
*tctx
,
426 struct smb2_tree
*tree
)
429 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
431 struct smb2_handle _h
;
432 struct smb2_handle
*h
= NULL
;
433 struct smb2_create io
;
435 struct smb2_tree
*tree2
= NULL
;
436 struct smb2_tree
*tree3
= NULL
;
437 uint64_t previous_session_id
;
438 struct smbcli_options options
;
439 struct GUID orig_client_guid
;
441 options
= tree
->session
->transport
->options
;
442 orig_client_guid
= options
.client_guid
;
444 /* Choose a random name in case the state is left a little funky. */
445 snprintf(fname
, 256, "durable_open_reopen1a_%s.dat",
446 generate_random_str(tctx
, 8));
448 smb2_util_unlink(tree
, fname
);
450 smb2_oplock_create_share(&io
, fname
,
451 smb2_util_share_access(""),
452 smb2_util_oplock_level("b"));
453 io
.in
.durable_open
= true;
455 status
= smb2_create(tree
, mem_ctx
, &io
);
456 CHECK_STATUS(status
, NT_STATUS_OK
);
457 _h
= io
.out
.file
.handle
;
459 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
460 CHECK_VAL(io
.out
.durable_open
, true);
461 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
464 * a session reconnect on a second tcp connection
467 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
469 /* for oplocks, the client guid can be different: */
470 options
.client_guid
= GUID_random();
472 ret
= torture_smb2_connection_ext(tctx
, previous_session_id
,
474 torture_assert_goto(tctx
, ret
, ret
, done
, "could not reconnect");
477 * check that this has deleted the old session
482 io
.in
.durable_handle
= h
;
484 status
= smb2_create(tree
, mem_ctx
, &io
);
485 CHECK_STATUS(status
, NT_STATUS_USER_SESSION_DELETED
);
490 * but a durable reconnect on the new session succeeds:
495 io
.in
.durable_handle
= h
;
497 status
= smb2_create(tree2
, mem_ctx
, &io
);
498 CHECK_STATUS(status
, NT_STATUS_OK
);
499 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
500 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
501 _h
= io
.out
.file
.handle
;
505 * a session reconnect on a second tcp connection
508 previous_session_id
= smb2cli_session_current_id(tree2
->session
->smbXcli
);
510 /* the original client_guid works just the same */
511 options
.client_guid
= orig_client_guid
;
513 ret
= torture_smb2_connection_ext(tctx
, previous_session_id
,
515 torture_assert_goto(tctx
, ret
, ret
, done
, "could not reconnect");
518 * check that this has deleted the old session
523 io
.in
.durable_handle
= h
;
525 status
= smb2_create(tree2
, mem_ctx
, &io
);
526 CHECK_STATUS(status
, NT_STATUS_USER_SESSION_DELETED
);
531 * but a durable reconnect on the new session succeeds:
536 io
.in
.durable_handle
= h
;
538 status
= smb2_create(tree3
, mem_ctx
, &io
);
539 CHECK_STATUS(status
, NT_STATUS_OK
);
540 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
541 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
542 _h
= io
.out
.file
.handle
;
556 smb2_util_close(tree
, *h
);
559 smb2_util_unlink(tree
, fname
);
564 talloc_free(mem_ctx
);
570 * lease variant of reopen1a
572 * Basic test for doing a durable open and doing a session
573 * reconnect while the first session is still active and the
574 * handle is still open in the client.
575 * This closes the original session and a durable reconnect on
576 * the new session succeeds depending on the client guid:
578 * Durable reconnect on a session with a different client guid fails.
579 * Durable reconnect on a session with the original client guid succeeds.
581 bool test_durable_open_reopen1a_lease(struct torture_context
*tctx
,
582 struct smb2_tree
*tree
)
585 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
587 struct smb2_handle _h
;
588 struct smb2_handle
*h
= NULL
;
589 struct smb2_create io
;
590 struct smb2_lease ls
;
593 struct smb2_tree
*tree2
= NULL
;
594 struct smb2_tree
*tree3
= NULL
;
595 uint64_t previous_session_id
;
596 struct smbcli_options options
;
597 struct GUID orig_client_guid
;
599 options
= tree
->session
->transport
->options
;
600 orig_client_guid
= options
.client_guid
;
602 /* Choose a random name in case the state is left a little funky. */
603 snprintf(fname
, 256, "durable_v2_open_reopen1a_lease_%s.dat",
604 generate_random_str(tctx
, 8));
606 smb2_util_unlink(tree
, fname
);
608 lease_key
= random();
609 smb2_lease_create(&io
, &ls
, false /* dir */, fname
,
610 lease_key
, smb2_util_lease_state("RWH"));
611 io
.in
.durable_open
= true;
613 status
= smb2_create(tree
, mem_ctx
, &io
);
614 CHECK_STATUS(status
, NT_STATUS_OK
);
615 _h
= io
.out
.file
.handle
;
617 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
618 CHECK_VAL(io
.out
.durable_open
, true);
619 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
620 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease_key
);
621 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease_key
);
622 CHECK_VAL(io
.out
.lease_response
.lease_state
,
623 smb2_util_lease_state("RWH"));
624 CHECK_VAL(io
.out
.lease_response
.lease_flags
, 0);
625 CHECK_VAL(io
.out
.lease_response
.lease_duration
, 0);
627 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
630 * a session reconnect on a second tcp connection
631 * with a different client_guid does not allow
632 * the durable reconnect.
635 options
.client_guid
= GUID_random();
637 ret
= torture_smb2_connection_ext(tctx
, previous_session_id
,
639 torture_assert_goto(tctx
, ret
, ret
, done
, "couldn't reconnect");
642 * check that this has deleted the old session
647 io
.in
.durable_handle
= h
;
648 io
.in
.lease_request
= &ls
;
649 status
= smb2_create(tree
, mem_ctx
, &io
);
650 CHECK_STATUS(status
, NT_STATUS_USER_SESSION_DELETED
);
655 * but a durable reconnect on the new session with the wrong
661 io
.in
.durable_handle
= h
;
662 io
.in
.lease_request
= &ls
;
663 status
= smb2_create(tree2
, mem_ctx
, &io
);
664 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
667 * now a session reconnect on a second tcp connection
668 * with original client_guid allows the durable reconnect.
671 options
.client_guid
= orig_client_guid
;
673 ret
= torture_smb2_connection_ext(tctx
, previous_session_id
,
675 torture_assert_goto(tctx
, ret
, ret
, done
, "couldn't reconnect");
678 * check that this has deleted the old session
679 * In this case, a durable reconnect attempt with the
680 * correct client_guid yields a different error code.
685 io
.in
.durable_handle
= h
;
686 io
.in
.lease_request
= &ls
;
687 status
= smb2_create(tree2
, mem_ctx
, &io
);
688 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
692 * but a durable reconnect on the new session succeeds:
697 io
.in
.durable_handle
= h
;
698 io
.in
.lease_request
= &ls
;
699 status
= smb2_create(tree3
, mem_ctx
, &io
);
700 CHECK_STATUS(status
, NT_STATUS_OK
);
701 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
702 CHECK_VAL(io
.out
.durable_open
, false); /* no dh response context... */
703 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
704 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease_key
);
705 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease_key
);
706 CHECK_VAL(io
.out
.lease_response
.lease_state
,
707 smb2_util_lease_state("RWH"));
708 CHECK_VAL(io
.out
.lease_response
.lease_flags
, 0);
709 CHECK_VAL(io
.out
.lease_response
.lease_duration
, 0);
710 _h
= io
.out
.file
.handle
;
724 smb2_util_close(tree
, *h
);
727 smb2_util_unlink(tree
, fname
);
732 talloc_free(mem_ctx
);
739 * basic test for doing a durable open
740 * tcp disconnect, reconnect, do a durable reopen (succeeds)
742 static bool test_durable_open_reopen2(struct torture_context
*tctx
,
743 struct smb2_tree
*tree
)
746 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
748 struct smb2_handle _h
;
749 struct smb2_handle
*h
= NULL
;
750 struct smb2_create io
;
753 /* Choose a random name in case the state is left a little funky. */
754 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
755 generate_random_str(tctx
, 8));
757 smb2_util_unlink(tree
, fname
);
759 smb2_oplock_create_share(&io
, fname
,
760 smb2_util_share_access(""),
761 smb2_util_oplock_level("b"));
762 io
.in
.durable_open
= true;
764 status
= smb2_create(tree
, mem_ctx
, &io
);
765 CHECK_STATUS(status
, NT_STATUS_OK
);
766 _h
= io
.out
.file
.handle
;
768 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
769 CHECK_VAL(io
.out
.durable_open
, true);
770 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
772 /* disconnect, leaving the durable in place */
775 if (!torture_smb2_connection(tctx
, &tree
)) {
776 torture_warning(tctx
, "couldn't reconnect, bailing\n");
782 /* the path name is ignored by the server */
784 io
.in
.durable_handle
= h
; /* durable v1 reconnect request */
787 status
= smb2_create(tree
, mem_ctx
, &io
);
788 CHECK_STATUS(status
, NT_STATUS_OK
);
789 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
790 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
791 _h
= io
.out
.file
.handle
;
794 /* disconnect again, leaving the durable in place */
797 if (!torture_smb2_connection(tctx
, &tree
)) {
798 torture_warning(tctx
, "couldn't reconnect, bailing\n");
804 * show that the filename and many other fields
805 * are ignored. only the reconnect request blob
809 /* the path name is ignored by the server */
810 io
.in
.security_flags
= 0x78;
811 io
.in
.oplock_level
= 0x78;
812 io
.in
.impersonation_level
= 0x12345678;
813 io
.in
.create_flags
= 0x12345678;
814 io
.in
.reserved
= 0x12345678;
815 io
.in
.desired_access
= 0x12345678;
816 io
.in
.file_attributes
= 0x12345678;
817 io
.in
.share_access
= 0x12345678;
818 io
.in
.create_disposition
= 0x12345678;
819 io
.in
.create_options
= 0x12345678;
820 io
.in
.fname
= "__non_existing_fname__";
821 io
.in
.durable_handle
= h
; /* durable v1 reconnect request */
824 status
= smb2_create(tree
, mem_ctx
, &io
);
825 CHECK_STATUS(status
, NT_STATUS_OK
);
826 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
827 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
828 _h
= io
.out
.file
.handle
;
831 /* disconnect, leaving the durable in place */
834 if (!torture_smb2_connection(tctx
, &tree
)) {
835 torture_warning(tctx
, "couldn't reconnect, bailing\n");
841 * show that an additionally specified durable v1 request
842 * is ignored by the server.
843 * See MS-SMB2, 3.3.5.9.7
844 * Handling the SMB2_CREATE_DURABLE_HANDLE_RECONNECT Create Context
847 /* the path name is ignored by the server */
849 io
.in
.durable_handle
= h
; /* durable v1 reconnect request */
850 io
.in
.durable_open
= true; /* durable v1 handle request */
853 status
= smb2_create(tree
, mem_ctx
, &io
);
854 CHECK_STATUS(status
, NT_STATUS_OK
);
855 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
856 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
857 _h
= io
.out
.file
.handle
;
863 smb2_util_close(tree
, *h
);
866 smb2_util_unlink(tree
, fname
);
871 talloc_free(mem_ctx
);
877 * lease variant of reopen2
878 * basic test for doing a durable open
879 * tcp disconnect, reconnect, do a durable reopen (succeeds)
881 static bool test_durable_open_reopen2_lease(struct torture_context
*tctx
,
882 struct smb2_tree
*tree
)
885 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
887 struct smb2_handle _h
;
888 struct smb2_handle
*h
= NULL
;
889 struct smb2_create io
;
890 struct smb2_lease ls
;
893 struct smbcli_options options
;
896 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
897 if (!(caps
& SMB2_CAP_LEASING
)) {
898 torture_skip(tctx
, "leases are not supported");
901 options
= tree
->session
->transport
->options
;
903 /* Choose a random name in case the state is left a little funky. */
904 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
905 generate_random_str(tctx
, 8));
907 smb2_util_unlink(tree
, fname
);
909 lease_key
= random();
910 smb2_lease_create(&io
, &ls
, false /* dir */, fname
, lease_key
,
911 smb2_util_lease_state("RWH"));
912 io
.in
.durable_open
= true;
914 status
= smb2_create(tree
, mem_ctx
, &io
);
915 CHECK_STATUS(status
, NT_STATUS_OK
);
916 _h
= io
.out
.file
.handle
;
918 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
920 CHECK_VAL(io
.out
.durable_open
, true);
921 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
922 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease_key
);
923 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease_key
);
924 CHECK_VAL(io
.out
.lease_response
.lease_state
,
925 smb2_util_lease_state("RWH"));
926 CHECK_VAL(io
.out
.lease_response
.lease_flags
, 0);
927 CHECK_VAL(io
.out
.lease_response
.lease_duration
, 0);
929 /* disconnect, reconnect and then do durable reopen */
932 if (!torture_smb2_connection_ext(tctx
, 0, &options
, &tree
)) {
933 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
= 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
= h
;
956 status
= smb2_create(tree
, mem_ctx
, &io
);
957 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
961 io
.in
.durable_handle
= 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
= false;
972 io
.in
.durable_handle
= h
;
973 io
.in
.lease_request
= &ls
;
974 io
.in
.oplock_level
= SMB2_OPLOCK_LEVEL_LEASE
;
975 /* a wrong lease key lets the request fail */
976 ls
.lease_key
.data
[0]++;
978 status
= smb2_create(tree
, mem_ctx
, &io
);
979 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
981 /* restore the correct lease key */
982 ls
.lease_key
.data
[0]--;
985 * this last failing attempt is almost correct:
986 * only problem is: we use the wrong filename...
987 * Note that this gives INVALID_PARAMETER.
988 * This is different from oplocks!
991 io
.in
.fname
= "__non_existing_fname__";
992 io
.in
.durable_open
= false;
993 io
.in
.durable_handle
= h
;
994 io
.in
.lease_request
= &ls
;
995 io
.in
.oplock_level
= SMB2_OPLOCK_LEVEL_LEASE
;
997 status
= smb2_create(tree
, mem_ctx
, &io
);
998 CHECK_STATUS(status
, NT_STATUS_INVALID_PARAMETER
);
1001 * Now for a succeeding reconnect:
1005 io
.in
.fname
= fname
;
1006 io
.in
.durable_open
= false;
1007 io
.in
.durable_handle
= h
;
1008 io
.in
.lease_request
= &ls
;
1009 io
.in
.oplock_level
= SMB2_OPLOCK_LEVEL_LEASE
;
1011 /* the requested lease state is irrelevant */
1012 ls
.lease_state
= smb2_util_lease_state("");
1016 status
= smb2_create(tree
, mem_ctx
, &io
);
1017 CHECK_STATUS(status
, NT_STATUS_OK
);
1019 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
1020 CHECK_VAL(io
.out
.durable_open
, false);
1021 CHECK_VAL(io
.out
.durable_open_v2
, false); /* no dh2q response blob */
1022 CHECK_VAL(io
.out
.persistent_open
, false);
1023 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1024 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease_key
);
1025 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease_key
);
1026 CHECK_VAL(io
.out
.lease_response
.lease_state
,
1027 smb2_util_lease_state("RWH"));
1028 CHECK_VAL(io
.out
.lease_response
.lease_flags
, 0);
1029 CHECK_VAL(io
.out
.lease_response
.lease_duration
, 0);
1030 _h
= io
.out
.file
.handle
;
1033 /* disconnect one more time */
1036 if (!torture_smb2_connection_ext(tctx
, 0, &options
, &tree
)) {
1037 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1043 * demonstrate that various parameters are ignored
1049 * These are completely ignored by the server
1051 io
.in
.security_flags
= 0x78;
1052 io
.in
.oplock_level
= 0x78;
1053 io
.in
.impersonation_level
= 0x12345678;
1054 io
.in
.create_flags
= 0x12345678;
1055 io
.in
.reserved
= 0x12345678;
1056 io
.in
.desired_access
= 0x12345678;
1057 io
.in
.file_attributes
= 0x12345678;
1058 io
.in
.share_access
= 0x12345678;
1059 io
.in
.create_disposition
= 0x12345678;
1060 io
.in
.create_options
= 0x12345678;
1063 * only these are checked:
1065 * - io.in.durable_handle,
1066 * - io.in.lease_request->lease_key
1069 io
.in
.fname
= fname
;
1070 io
.in
.durable_open_v2
= false;
1071 io
.in
.durable_handle_v2
= h
;
1072 io
.in
.lease_request
= &ls
;
1074 /* the requested lease state is irrelevant */
1075 ls
.lease_state
= smb2_util_lease_state("");
1079 status
= smb2_create(tree
, mem_ctx
, &io
);
1080 CHECK_STATUS(status
, NT_STATUS_OK
);
1082 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
1083 CHECK_VAL(io
.out
.durable_open
, false);
1084 CHECK_VAL(io
.out
.durable_open_v2
, false); /* no dh2q response blob */
1085 CHECK_VAL(io
.out
.persistent_open
, false);
1086 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1087 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease_key
);
1088 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease_key
);
1089 CHECK_VAL(io
.out
.lease_response
.lease_state
,
1090 smb2_util_lease_state("RWH"));
1091 CHECK_VAL(io
.out
.lease_response
.lease_flags
, 0);
1092 CHECK_VAL(io
.out
.lease_response
.lease_duration
, 0);
1094 _h
= io
.out
.file
.handle
;
1100 smb2_util_close(tree
, *h
);
1103 smb2_util_unlink(tree
, fname
);
1108 talloc_free(mem_ctx
);
1114 * lease v2 variant of reopen2
1115 * basic test for doing a durable open
1116 * tcp disconnect, reconnect, do a durable reopen (succeeds)
1118 static bool test_durable_open_reopen2_lease_v2(struct torture_context
*tctx
,
1119 struct smb2_tree
*tree
)
1122 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1124 struct smb2_handle _h
;
1125 struct smb2_handle
*h
= NULL
;
1126 struct smb2_create io
;
1127 struct smb2_lease ls
;
1130 struct smbcli_options options
;
1133 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
1134 if (!(caps
& SMB2_CAP_LEASING
)) {
1135 torture_skip(tctx
, "leases are not supported");
1138 options
= tree
->session
->transport
->options
;
1140 /* Choose a random name in case the state is left a little funky. */
1141 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
1142 generate_random_str(tctx
, 8));
1144 smb2_util_unlink(tree
, fname
);
1146 lease_key
= random();
1147 smb2_lease_v2_create(&io
, &ls
, false /* dir */, fname
,
1148 lease_key
, 0, /* parent lease key */
1149 smb2_util_lease_state("RWH"), 0 /* lease epoch */);
1150 io
.in
.durable_open
= true;
1152 status
= smb2_create(tree
, mem_ctx
, &io
);
1153 CHECK_STATUS(status
, NT_STATUS_OK
);
1154 _h
= io
.out
.file
.handle
;
1156 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1158 CHECK_VAL(io
.out
.durable_open
, true);
1159 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1160 CHECK_VAL(io
.out
.lease_response_v2
.lease_key
.data
[0], lease_key
);
1161 CHECK_VAL(io
.out
.lease_response_v2
.lease_key
.data
[1], ~lease_key
);
1162 CHECK_VAL(io
.out
.lease_response_v2
.lease_state
,
1163 smb2_util_lease_state("RWH"));
1164 CHECK_VAL(io
.out
.lease_response_v2
.lease_flags
, 0);
1165 CHECK_VAL(io
.out
.lease_response_v2
.lease_duration
, 0);
1167 /* disconnect, reconnect and then do durable reopen */
1170 if (!torture_smb2_connection_ext(tctx
, 0, &options
, &tree
)) {
1171 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1176 /* a few failure tests: */
1179 * several attempts without lease attached:
1180 * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
1181 * irrespective of file name provided
1186 io
.in
.durable_handle
= h
;
1187 status
= smb2_create(tree
, mem_ctx
, &io
);
1188 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1191 io
.in
.fname
= "__non_existing_fname__";
1192 io
.in
.durable_handle
= h
;
1193 status
= smb2_create(tree
, mem_ctx
, &io
);
1194 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1197 io
.in
.fname
= fname
;
1198 io
.in
.durable_handle
= h
;
1199 status
= smb2_create(tree
, mem_ctx
, &io
);
1200 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1203 * attempt with lease provided, but
1204 * with a changed lease key. => fails
1207 io
.in
.fname
= fname
;
1208 io
.in
.durable_open
= false;
1209 io
.in
.durable_handle
= h
;
1210 io
.in
.lease_request_v2
= &ls
;
1211 io
.in
.oplock_level
= SMB2_OPLOCK_LEVEL_LEASE
;
1212 /* a wrong lease key lets the request fail */
1213 ls
.lease_key
.data
[0]++;
1215 status
= smb2_create(tree
, mem_ctx
, &io
);
1216 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1218 /* restore the correct lease key */
1219 ls
.lease_key
.data
[0]--;
1222 * this last failing attempt is almost correct:
1223 * only problem is: we use the wrong filename...
1224 * Note that this gives INVALID_PARAMETER.
1225 * This is different from oplocks!
1228 io
.in
.fname
= "__non_existing_fname__";
1229 io
.in
.durable_open
= false;
1230 io
.in
.durable_handle
= h
;
1231 io
.in
.lease_request_v2
= &ls
;
1232 io
.in
.oplock_level
= SMB2_OPLOCK_LEVEL_LEASE
;
1234 status
= smb2_create(tree
, mem_ctx
, &io
);
1235 CHECK_STATUS(status
, NT_STATUS_INVALID_PARAMETER
);
1238 * Now for a succeeding reconnect:
1242 io
.in
.fname
= fname
;
1243 io
.in
.durable_open
= false;
1244 io
.in
.durable_handle
= h
;
1245 io
.in
.lease_request_v2
= &ls
;
1246 io
.in
.oplock_level
= SMB2_OPLOCK_LEVEL_LEASE
;
1248 /* the requested lease state is irrelevant */
1249 ls
.lease_state
= smb2_util_lease_state("");
1253 status
= smb2_create(tree
, mem_ctx
, &io
);
1254 CHECK_STATUS(status
, NT_STATUS_OK
);
1256 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
1257 CHECK_VAL(io
.out
.durable_open
, false);
1258 CHECK_VAL(io
.out
.durable_open_v2
, false); /* no dh2q response blob */
1259 CHECK_VAL(io
.out
.persistent_open
, false);
1260 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1261 CHECK_VAL(io
.out
.lease_response_v2
.lease_key
.data
[0], lease_key
);
1262 CHECK_VAL(io
.out
.lease_response_v2
.lease_key
.data
[1], ~lease_key
);
1263 CHECK_VAL(io
.out
.lease_response_v2
.lease_state
,
1264 smb2_util_lease_state("RWH"));
1265 CHECK_VAL(io
.out
.lease_response_v2
.lease_flags
, 0);
1266 CHECK_VAL(io
.out
.lease_response_v2
.lease_duration
, 0);
1267 _h
= io
.out
.file
.handle
;
1270 /* disconnect one more time */
1273 if (!torture_smb2_connection_ext(tctx
, 0, &options
, &tree
)) {
1274 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1280 * demonstrate that various parameters are ignored
1286 * These are completely ignored by the server
1288 io
.in
.security_flags
= 0x78;
1289 io
.in
.oplock_level
= 0x78;
1290 io
.in
.impersonation_level
= 0x12345678;
1291 io
.in
.create_flags
= 0x12345678;
1292 io
.in
.reserved
= 0x12345678;
1293 io
.in
.desired_access
= 0x12345678;
1294 io
.in
.file_attributes
= 0x12345678;
1295 io
.in
.share_access
= 0x12345678;
1296 io
.in
.create_disposition
= 0x12345678;
1297 io
.in
.create_options
= 0x12345678;
1300 * only these are checked:
1302 * - io.in.durable_handle,
1303 * - io.in.lease_request->lease_key
1306 io
.in
.fname
= fname
;
1307 io
.in
.durable_open_v2
= false;
1308 io
.in
.durable_handle_v2
= h
;
1309 io
.in
.lease_request_v2
= &ls
;
1311 /* the requested lease state is irrelevant */
1312 ls
.lease_state
= smb2_util_lease_state("");
1316 status
= smb2_create(tree
, mem_ctx
, &io
);
1317 CHECK_STATUS(status
, NT_STATUS_OK
);
1319 CHECK_CREATED(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
1320 CHECK_VAL(io
.out
.durable_open
, false);
1321 CHECK_VAL(io
.out
.durable_open_v2
, false); /* no dh2q response blob */
1322 CHECK_VAL(io
.out
.persistent_open
, false);
1323 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
1324 CHECK_VAL(io
.out
.lease_response_v2
.lease_key
.data
[0], lease_key
);
1325 CHECK_VAL(io
.out
.lease_response_v2
.lease_key
.data
[1], ~lease_key
);
1326 CHECK_VAL(io
.out
.lease_response_v2
.lease_state
,
1327 smb2_util_lease_state("RWH"));
1328 CHECK_VAL(io
.out
.lease_response_v2
.lease_flags
, 0);
1329 CHECK_VAL(io
.out
.lease_response_v2
.lease_duration
, 0);
1331 _h
= io
.out
.file
.handle
;
1337 smb2_util_close(tree
, *h
);
1340 smb2_util_unlink(tree
, fname
);
1345 talloc_free(mem_ctx
);
1351 * basic test for doing a durable open
1352 * tcp disconnect, reconnect with a session reconnect and
1353 * do a durable reopen (succeeds)
1355 static bool test_durable_open_reopen2a(struct torture_context
*tctx
,
1356 struct smb2_tree
*tree
)
1359 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1361 struct smb2_handle _h
;
1362 struct smb2_handle
*h
= NULL
;
1363 struct smb2_create io1
, io2
;
1364 uint64_t previous_session_id
;
1366 struct smbcli_options options
;
1368 options
= tree
->session
->transport
->options
;
1370 /* Choose a random name in case the state is left a little funky. */
1371 snprintf(fname
, 256, "durable_open_reopen2_%s.dat",
1372 generate_random_str(tctx
, 8));
1374 smb2_util_unlink(tree
, fname
);
1376 smb2_oplock_create_share(&io1
, fname
,
1377 smb2_util_share_access(""),
1378 smb2_util_oplock_level("b"));
1379 io1
.in
.durable_open
= true;
1381 status
= smb2_create(tree
, mem_ctx
, &io1
);
1382 CHECK_STATUS(status
, NT_STATUS_OK
);
1383 _h
= io1
.out
.file
.handle
;
1385 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1386 CHECK_VAL(io1
.out
.durable_open
, true);
1387 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
1389 /* disconnect, reconnect and then do durable reopen */
1390 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
1394 if (!torture_smb2_connection_ext(tctx
, previous_session_id
,
1397 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1403 io2
.in
.fname
= fname
;
1404 io2
.in
.durable_handle
= h
;
1407 status
= smb2_create(tree
, mem_ctx
, &io2
);
1408 CHECK_STATUS(status
, NT_STATUS_OK
);
1409 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
1410 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
1411 _h
= io2
.out
.file
.handle
;
1417 smb2_util_close(tree
, *h
);
1420 smb2_util_unlink(tree
, fname
);
1425 talloc_free(mem_ctx
);
1432 * basic test for doing a durable open:
1433 * tdis, new tcon, try durable reopen (fails)
1435 static bool test_durable_open_reopen3(struct torture_context
*tctx
,
1436 struct smb2_tree
*tree
)
1439 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1441 struct smb2_handle _h
;
1442 struct smb2_handle
*h
= NULL
;
1443 struct smb2_create io1
, io2
;
1445 struct smb2_tree
*tree2
;
1447 /* Choose a random name in case the state is left a little funky. */
1448 snprintf(fname
, 256, "durable_open_reopen3_%s.dat",
1449 generate_random_str(tctx
, 8));
1451 smb2_util_unlink(tree
, fname
);
1453 smb2_oplock_create_share(&io1
, fname
,
1454 smb2_util_share_access(""),
1455 smb2_util_oplock_level("b"));
1456 io1
.in
.durable_open
= true;
1458 status
= smb2_create(tree
, mem_ctx
, &io1
);
1459 CHECK_STATUS(status
, NT_STATUS_OK
);
1460 _h
= io1
.out
.file
.handle
;
1462 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1463 CHECK_VAL(io1
.out
.durable_open
, true);
1464 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
1466 /* disconnect, reconnect and then do durable reopen */
1467 status
= smb2_tdis(tree
);
1468 CHECK_STATUS(status
, NT_STATUS_OK
);
1470 if (!torture_smb2_tree_connect(tctx
, tree
->session
, mem_ctx
, &tree2
)) {
1471 torture_warning(tctx
, "couldn't reconnect to share, bailing\n");
1478 io2
.in
.fname
= fname
;
1479 io2
.in
.durable_handle
= h
;
1481 status
= smb2_create(tree2
, mem_ctx
, &io2
);
1482 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1487 smb2_util_close(tree
, *h
);
1490 smb2_util_unlink(tree2
, fname
);
1495 talloc_free(mem_ctx
);
1501 * basic test for doing a durable open:
1502 * logoff, create a new session, do a durable reopen (succeeds)
1504 static bool test_durable_open_reopen4(struct torture_context
*tctx
,
1505 struct smb2_tree
*tree
)
1508 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1510 struct smb2_handle _h
;
1511 struct smb2_handle
*h
= NULL
;
1512 struct smb2_create io1
, io2
;
1514 struct smb2_transport
*transport
;
1515 struct smb2_session
*session2
;
1516 struct smb2_tree
*tree2
;
1518 /* Choose a random name in case the state is left a little funky. */
1519 snprintf(fname
, 256, "durable_open_reopen4_%s.dat",
1520 generate_random_str(tctx
, 8));
1522 smb2_util_unlink(tree
, fname
);
1524 smb2_oplock_create_share(&io1
, fname
,
1525 smb2_util_share_access(""),
1526 smb2_util_oplock_level("b"));
1527 io1
.in
.durable_open
= true;
1529 status
= smb2_create(tree
, mem_ctx
, &io1
);
1530 CHECK_STATUS(status
, NT_STATUS_OK
);
1531 _h
= io1
.out
.file
.handle
;
1533 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1534 CHECK_VAL(io1
.out
.durable_open
, true);
1535 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
1538 * do a session logoff, establish a new session and tree
1539 * connect on the same transport, and try a durable reopen
1541 transport
= tree
->session
->transport
;
1542 status
= smb2_logoff(tree
->session
);
1543 CHECK_STATUS(status
, NT_STATUS_OK
);
1545 if (!torture_smb2_session_setup(tctx
, transport
,
1546 0, /* previous_session_id */
1547 mem_ctx
, &session2
))
1549 torture_warning(tctx
, "session setup failed.\n");
1555 * the session setup has talloc-stolen the transport,
1556 * so we can safely free the old tree+session for clarity
1560 if (!torture_smb2_tree_connect(tctx
, session2
, mem_ctx
, &tree2
)) {
1561 torture_warning(tctx
, "tree connect failed.\n");
1567 io2
.in
.fname
= fname
;
1568 io2
.in
.durable_handle
= h
;
1571 status
= smb2_create(tree2
, mem_ctx
, &io2
);
1572 CHECK_STATUS(status
, NT_STATUS_OK
);
1574 _h
= io2
.out
.file
.handle
;
1576 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
1577 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
1582 smb2_util_close(tree2
, *h
);
1585 smb2_util_unlink(tree2
, fname
);
1590 talloc_free(mem_ctx
);
1595 static bool test_durable_open_delete_on_close1(struct torture_context
*tctx
,
1596 struct smb2_tree
*tree
)
1599 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1601 struct smb2_handle _h
;
1602 struct smb2_handle
*h
= NULL
;
1603 struct smb2_create io1
, io2
;
1607 /* Choose a random name in case the state is left a little funky. */
1608 snprintf(fname
, 256, "durable_open_delete_on_close1_%s.dat",
1609 generate_random_str(tctx
, 8));
1611 smb2_util_unlink(tree
, fname
);
1613 smb2_oplock_create_share(&io1
, fname
,
1614 smb2_util_share_access(""),
1615 smb2_util_oplock_level("b"));
1616 io1
.in
.durable_open
= true;
1617 io1
.in
.create_options
|= NTCREATEX_OPTIONS_DELETE_ON_CLOSE
;
1619 status
= smb2_create(tree
, mem_ctx
, &io1
);
1620 CHECK_STATUS(status
, NT_STATUS_OK
);
1621 _h
= io1
.out
.file
.handle
;
1623 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1624 CHECK_VAL(io1
.out
.durable_open
, true);
1625 CHECK_VAL(io1
.out
.oplock_level
, smb2_util_oplock_level("b"));
1627 status
= smb2_util_write(tree
, *h
, &b
, 0, 1);
1628 CHECK_STATUS(status
, NT_STATUS_OK
);
1630 /* disconnect, leaving the durable handle in place */
1633 if (!torture_smb2_connection(tctx
, &tree
)) {
1634 torture_warning(tctx
, "could not reconnect, bailing\n");
1640 * Open the file on the new connection again
1641 * and check that it has been newly created,
1642 * i.e. delete on close was effective on the disconnected handle.
1643 * Also check that the file is really empty,
1644 * the previously written byte gone.
1646 smb2_oplock_create_share(&io2
, fname
,
1647 smb2_util_share_access(""),
1648 smb2_util_oplock_level("b"));
1649 io2
.in
.create_options
|= NTCREATEX_OPTIONS_DELETE_ON_CLOSE
;
1651 status
= smb2_create(tree
, mem_ctx
, &io2
);
1652 CHECK_STATUS(status
, NT_STATUS_OK
);
1653 _h
= io2
.out
.file
.handle
;
1655 CHECK_CREATED_SIZE(&io2
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
, 0, 0);
1656 CHECK_VAL(io2
.out
.durable_open
, false);
1657 CHECK_VAL(io2
.out
.oplock_level
, smb2_util_oplock_level("b"));
1662 smb2_util_close(tree
, *h
);
1665 smb2_util_unlink(tree
, fname
);
1670 talloc_free(mem_ctx
);
1676 static bool test_durable_open_delete_on_close2(struct torture_context
*tctx
,
1677 struct smb2_tree
*tree
)
1680 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1682 struct smb2_handle _h
;
1683 struct smb2_handle
*h
= NULL
;
1684 struct smb2_create io
;
1687 uint64_t previous_session_id
;
1688 uint64_t alloc_size_step
;
1689 struct smbcli_options options
;
1691 options
= tree
->session
->transport
->options
;
1693 /* Choose a random name in case the state is left a little funky. */
1694 snprintf(fname
, 256, "durable_open_delete_on_close2_%s.dat",
1695 generate_random_str(tctx
, 8));
1697 smb2_util_unlink(tree
, fname
);
1699 smb2_oplock_create_share(&io
, fname
,
1700 smb2_util_share_access(""),
1701 smb2_util_oplock_level("b"));
1702 io
.in
.durable_open
= true;
1703 io
.in
.create_options
|= NTCREATEX_OPTIONS_DELETE_ON_CLOSE
;
1705 status
= smb2_create(tree
, mem_ctx
, &io
);
1706 CHECK_STATUS(status
, NT_STATUS_OK
);
1707 _h
= io
.out
.file
.handle
;
1709 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1710 CHECK_VAL(io
.out
.durable_open
, true);
1711 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
1713 status
= smb2_util_write(tree
, *h
, &b
, 0, 1);
1714 CHECK_STATUS(status
, NT_STATUS_OK
);
1716 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
1718 /* disconnect, leaving the durable handle in place */
1721 if (!torture_smb2_connection_ext(tctx
, previous_session_id
,
1724 torture_warning(tctx
, "could not reconnect, bailing\n");
1730 io
.in
.fname
= fname
;
1731 io
.in
.durable_handle
= h
;
1733 status
= smb2_create(tree
, mem_ctx
, &io
);
1734 CHECK_STATUS(status
, NT_STATUS_OK
);
1735 _h
= io
.out
.file
.handle
;
1737 alloc_size_step
= io
.out
.alloc_size
;
1738 CHECK_CREATED_SIZE(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
, alloc_size_step
, 1);
1739 CHECK_VAL(io
.out
.durable_open
, false);
1740 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
1742 /* close the file, thereby deleting it */
1743 smb2_util_close(tree
, *h
);
1744 status
= smb2_logoff(tree
->session
);
1747 if (!torture_smb2_connection(tctx
, &tree
)) {
1748 torture_warning(tctx
, "could not reconnect, bailing\n");
1754 * Open the file on the new connection again
1755 * and check that it has been newly created,
1756 * i.e. delete on close was effective on the reconnected handle.
1757 * Also check that the file is really empty,
1758 * the previously written byte gone.
1760 smb2_oplock_create_share(&io
, fname
,
1761 smb2_util_share_access(""),
1762 smb2_util_oplock_level("b"));
1763 io
.in
.durable_open
= true;
1764 io
.in
.create_options
|= NTCREATEX_OPTIONS_DELETE_ON_CLOSE
;
1766 status
= smb2_create(tree
, mem_ctx
, &io
);
1767 CHECK_STATUS(status
, NT_STATUS_OK
);
1768 _h
= io
.out
.file
.handle
;
1770 CHECK_CREATED_SIZE(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
, 0, 0);
1771 CHECK_VAL(io
.out
.durable_open
, true);
1772 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
1777 smb2_util_close(tree
, *h
);
1780 smb2_util_unlink(tree
, fname
);
1785 talloc_free(mem_ctx
);
1791 basic testing of SMB2 durable opens
1792 regarding the position information on the handle
1794 static bool test_durable_open_file_position(struct torture_context
*tctx
,
1795 struct smb2_tree
*tree
)
1797 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1798 struct smb2_handle h
;
1799 struct smb2_create io
;
1801 const char *fname
= "durable_open_position.dat";
1802 union smb_fileinfo qfinfo
;
1803 union smb_setfileinfo sfinfo
;
1806 uint64_t previous_session_id
;
1807 struct smbcli_options options
;
1809 options
= tree
->session
->transport
->options
;
1811 smb2_util_unlink(tree
, fname
);
1813 smb2_oplock_create(&io
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
1814 io
.in
.durable_open
= true;
1816 status
= smb2_create(tree
, mem_ctx
, &io
);
1817 CHECK_STATUS(status
, NT_STATUS_OK
);
1818 h
= io
.out
.file
.handle
;
1819 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1820 CHECK_VAL(io
.out
.durable_open
, true);
1821 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
1823 /* TODO: check extra blob content */
1825 ZERO_STRUCT(qfinfo
);
1826 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
1827 qfinfo
.generic
.in
.file
.handle
= h
;
1828 status
= smb2_getinfo_file(tree
, mem_ctx
, &qfinfo
);
1829 CHECK_STATUS(status
, NT_STATUS_OK
);
1830 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0);
1831 pos
= qfinfo
.position_information
.out
.position
;
1832 torture_comment(tctx
, "position: %llu\n",
1833 (unsigned long long)pos
);
1835 ZERO_STRUCT(sfinfo
);
1836 sfinfo
.generic
.level
= RAW_SFILEINFO_POSITION_INFORMATION
;
1837 sfinfo
.generic
.in
.file
.handle
= h
;
1838 sfinfo
.position_information
.in
.position
= 0x1000;
1839 status
= smb2_setinfo_file(tree
, &sfinfo
);
1840 CHECK_STATUS(status
, NT_STATUS_OK
);
1842 ZERO_STRUCT(qfinfo
);
1843 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
1844 qfinfo
.generic
.in
.file
.handle
= h
;
1845 status
= smb2_getinfo_file(tree
, mem_ctx
, &qfinfo
);
1846 CHECK_STATUS(status
, NT_STATUS_OK
);
1847 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0x1000);
1848 pos
= qfinfo
.position_information
.out
.position
;
1849 torture_comment(tctx
, "position: %llu\n",
1850 (unsigned long long)pos
);
1852 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
1854 /* tcp disconnect */
1858 /* do a session reconnect */
1859 if (!torture_smb2_connection_ext(tctx
, previous_session_id
,
1862 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1867 ZERO_STRUCT(qfinfo
);
1868 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
1869 qfinfo
.generic
.in
.file
.handle
= h
;
1870 status
= smb2_getinfo_file(tree
, mem_ctx
, &qfinfo
);
1871 CHECK_STATUS(status
, NT_STATUS_FILE_CLOSED
);
1874 io
.in
.fname
= fname
;
1875 io
.in
.durable_handle
= &h
;
1877 status
= smb2_create(tree
, mem_ctx
, &io
);
1878 CHECK_STATUS(status
, NT_STATUS_OK
);
1879 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
1880 CHECK_VAL(io
.out
.reserved
, 0x00);
1881 CHECK_VAL(io
.out
.create_action
, NTCREATEX_ACTION_EXISTED
);
1882 CHECK_VAL(io
.out
.alloc_size
, 0);
1883 CHECK_VAL(io
.out
.size
, 0);
1884 CHECK_VAL(io
.out
.file_attr
, FILE_ATTRIBUTE_ARCHIVE
);
1885 CHECK_VAL(io
.out
.reserved2
, 0);
1887 h
= io
.out
.file
.handle
;
1889 ZERO_STRUCT(qfinfo
);
1890 qfinfo
.generic
.level
= RAW_FILEINFO_POSITION_INFORMATION
;
1891 qfinfo
.generic
.in
.file
.handle
= h
;
1892 status
= smb2_getinfo_file(tree
, mem_ctx
, &qfinfo
);
1893 CHECK_STATUS(status
, NT_STATUS_OK
);
1894 CHECK_VAL(qfinfo
.position_information
.out
.position
, 0x1000);
1895 pos
= qfinfo
.position_information
.out
.position
;
1896 torture_comment(tctx
, "position: %llu\n",
1897 (unsigned long long)pos
);
1899 smb2_util_close(tree
, h
);
1901 talloc_free(mem_ctx
);
1903 smb2_util_unlink(tree
, fname
);
1912 Open, disconnect, oplock break, reconnect.
1914 static bool test_durable_open_oplock(struct torture_context
*tctx
,
1915 struct smb2_tree
*tree1
,
1916 struct smb2_tree
*tree2
)
1918 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1919 struct smb2_create io1
, io2
;
1920 struct smb2_handle h1
= {{0}};
1921 struct smb2_handle h2
= {{0}};
1926 /* Choose a random name in case the state is left a little funky. */
1927 snprintf(fname
, 256, "durable_open_oplock_%s.dat", generate_random_str(tctx
, 8));
1930 smb2_util_unlink(tree1
, fname
);
1932 /* Create with batch oplock */
1933 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
1934 io1
.in
.durable_open
= true;
1937 io2
.in
.create_disposition
= NTCREATEX_DISP_OPEN
;
1939 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1940 CHECK_STATUS(status
, NT_STATUS_OK
);
1941 h1
= io1
.out
.file
.handle
;
1942 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
1943 CHECK_VAL(io1
.out
.durable_open
, true);
1944 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
1946 /* Disconnect after getting the batch */
1951 * Windows7 (build 7000) will break a batch oplock immediately if the
1952 * original client is gone. (ZML: This seems like a bug. It should give
1953 * some time for the client to reconnect!)
1955 status
= smb2_create(tree2
, mem_ctx
, &io2
);
1956 CHECK_STATUS(status
, NT_STATUS_OK
);
1957 h2
= io2
.out
.file
.handle
;
1958 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
1959 CHECK_VAL(io2
.out
.durable_open
, true);
1960 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
1962 /* What if tree1 tries to come back and reclaim? */
1963 if (!torture_smb2_connection(tctx
, &tree1
)) {
1964 torture_warning(tctx
, "couldn't reconnect, bailing\n");
1970 io1
.in
.fname
= fname
;
1971 io1
.in
.durable_handle
= &h1
;
1973 status
= smb2_create(tree1
, mem_ctx
, &io1
);
1974 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
1977 smb2_util_close(tree2
, h2
);
1978 smb2_util_unlink(tree2
, fname
);
1987 Open, disconnect, lease break, reconnect.
1989 static bool test_durable_open_lease(struct torture_context
*tctx
,
1990 struct smb2_tree
*tree1
,
1991 struct smb2_tree
*tree2
)
1993 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
1994 struct smb2_create io1
, io2
;
1995 struct smb2_lease ls1
, ls2
;
1996 struct smb2_handle h1
= {{0}};
1997 struct smb2_handle h2
= {{0}};
2001 uint64_t lease1
, lease2
;
2004 caps
= smb2cli_conn_server_capabilities(tree1
->session
->transport
->conn
);
2005 if (!(caps
& SMB2_CAP_LEASING
)) {
2006 torture_skip(tctx
, "leases are not supported");
2010 * Choose a random name and random lease in case the state is left a
2015 snprintf(fname
, 256, "durable_open_lease_%s.dat", generate_random_str(tctx
, 8));
2018 smb2_util_unlink(tree1
, fname
);
2020 /* Create with lease */
2021 smb2_lease_create(&io1
, &ls1
, false /* dir */, fname
,
2022 lease1
, smb2_util_lease_state("RHW"));
2023 io1
.in
.durable_open
= true;
2025 smb2_lease_create(&io2
, &ls2
, false /* dir */, fname
,
2026 lease2
, smb2_util_lease_state("RHW"));
2027 io2
.in
.durable_open
= true;
2028 io2
.in
.create_disposition
= NTCREATEX_DISP_OPEN
;
2030 status
= smb2_create(tree1
, mem_ctx
, &io1
);
2031 CHECK_STATUS(status
, NT_STATUS_OK
);
2032 h1
= io1
.out
.file
.handle
;
2033 CHECK_VAL(io1
.out
.durable_open
, true);
2034 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
2036 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
2037 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[0], lease1
);
2038 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[1], ~lease1
);
2039 CHECK_VAL(io1
.out
.lease_response
.lease_state
,
2040 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
2042 /* Disconnect after getting the lease */
2047 * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?)
2048 * even if the original client is gone. (ZML: This seems like a bug. It
2049 * should give some time for the client to reconnect! And why RH?)
2051 * obnox: Current windows 7 and w2k8r2 grant RHW instead of RH.
2052 * Test is adapted accordingly.
2054 status
= smb2_create(tree2
, mem_ctx
, &io2
);
2055 CHECK_STATUS(status
, NT_STATUS_OK
);
2056 h2
= io2
.out
.file
.handle
;
2057 CHECK_VAL(io2
.out
.durable_open
, true);
2058 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
2060 CHECK_VAL(io2
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
2061 CHECK_VAL(io2
.out
.lease_response
.lease_key
.data
[0], lease2
);
2062 CHECK_VAL(io2
.out
.lease_response
.lease_key
.data
[1], ~lease2
);
2063 CHECK_VAL(io2
.out
.lease_response
.lease_state
,
2064 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
2066 /* What if tree1 tries to come back and reclaim? */
2067 if (!torture_smb2_connection(tctx
, &tree1
)) {
2068 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2074 io1
.in
.fname
= fname
;
2075 io1
.in
.durable_handle
= &h1
;
2076 io1
.in
.lease_request
= &ls1
;
2078 status
= smb2_create(tree1
, mem_ctx
, &io1
);
2079 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
2082 smb2_util_close(tree2
, h2
);
2083 smb2_util_unlink(tree2
, fname
);
2091 static bool test_durable_open_lock_oplock(struct torture_context
*tctx
,
2092 struct smb2_tree
*tree
)
2094 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
2095 struct smb2_create io
;
2096 struct smb2_handle h
= {{0}};
2097 struct smb2_lock lck
;
2098 struct smb2_lock_element el
[2];
2105 snprintf(fname
, 256, "durable_open_oplock_lock_%s.dat", generate_random_str(tctx
, 8));
2108 smb2_util_unlink(tree
, fname
);
2110 /* Create with oplock */
2112 smb2_oplock_create_share(&io
, fname
,
2113 smb2_util_share_access(""),
2114 smb2_util_oplock_level("b"));
2115 io
.in
.durable_open
= true;
2117 status
= smb2_create(tree
, mem_ctx
, &io
);
2118 CHECK_STATUS(status
, NT_STATUS_OK
);
2119 h
= io
.out
.file
.handle
;
2120 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
2122 CHECK_VAL(io
.out
.durable_open
, true);
2123 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
2128 lck
.in
.lock_count
= 0x0001;
2129 lck
.in
.lock_sequence
= 0x00000000;
2130 lck
.in
.file
.handle
= h
;
2133 el
[0].reserved
= 0x00000000;
2134 el
[0].flags
= SMB2_LOCK_FLAG_EXCLUSIVE
;
2135 status
= smb2_lock(tree
, &lck
);
2136 CHECK_STATUS(status
, NT_STATUS_OK
);
2138 /* Disconnect/Reconnect. */
2142 if (!torture_smb2_connection(tctx
, &tree
)) {
2143 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2149 io
.in
.fname
= fname
;
2150 io
.in
.durable_handle
= &h
;
2152 status
= smb2_create(tree
, mem_ctx
, &io
);
2153 CHECK_STATUS(status
, NT_STATUS_OK
);
2154 h
= io
.out
.file
.handle
;
2156 lck
.in
.file
.handle
= h
;
2157 el
[0].flags
= SMB2_LOCK_FLAG_UNLOCK
;
2158 status
= smb2_lock(tree
, &lck
);
2159 CHECK_STATUS(status
, NT_STATUS_OK
);
2162 smb2_util_close(tree
, h
);
2163 smb2_util_unlink(tree
, fname
);
2170 Open, take BRL, disconnect, reconnect.
2172 static bool test_durable_open_lock_lease(struct torture_context
*tctx
,
2173 struct smb2_tree
*tree
)
2175 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
2176 struct smb2_create io
;
2177 struct smb2_lease ls
;
2178 struct smb2_handle h
= {{0}};
2179 struct smb2_lock lck
;
2180 struct smb2_lock_element el
[2];
2186 struct smbcli_options options
;
2188 options
= tree
->session
->transport
->options
;
2190 caps
= smb2cli_conn_server_capabilities(tree
->session
->transport
->conn
);
2191 if (!(caps
& SMB2_CAP_LEASING
)) {
2192 torture_skip(tctx
, "leases are not supported");
2196 * Choose a random name and random lease in case the state is left a
2200 snprintf(fname
, 256, "durable_open_lease_lock_%s.dat", generate_random_str(tctx
, 8));
2203 smb2_util_unlink(tree
, fname
);
2205 /* Create with lease */
2207 smb2_lease_create(&io
, &ls
, false /* dir */, fname
, lease
,
2208 smb2_util_lease_state("RWH"));
2209 io
.in
.durable_open
= true;
2211 status
= smb2_create(tree
, mem_ctx
, &io
);
2212 CHECK_STATUS(status
, NT_STATUS_OK
);
2213 h
= io
.out
.file
.handle
;
2214 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
2216 CHECK_VAL(io
.out
.durable_open
, true);
2217 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
2218 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[0], lease
);
2219 CHECK_VAL(io
.out
.lease_response
.lease_key
.data
[1], ~lease
);
2220 CHECK_VAL(io
.out
.lease_response
.lease_state
,
2221 SMB2_LEASE_READ
|SMB2_LEASE_HANDLE
|SMB2_LEASE_WRITE
);
2226 lck
.in
.lock_count
= 0x0001;
2227 lck
.in
.lock_sequence
= 0x00000000;
2228 lck
.in
.file
.handle
= h
;
2231 el
[0].reserved
= 0x00000000;
2232 el
[0].flags
= SMB2_LOCK_FLAG_EXCLUSIVE
;
2233 status
= smb2_lock(tree
, &lck
);
2234 CHECK_STATUS(status
, NT_STATUS_OK
);
2236 /* Disconnect/Reconnect. */
2240 if (!torture_smb2_connection_ext(tctx
, 0, &options
, &tree
)) {
2241 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2247 io
.in
.fname
= fname
;
2248 io
.in
.durable_handle
= &h
;
2249 io
.in
.lease_request
= &ls
;
2251 status
= smb2_create(tree
, mem_ctx
, &io
);
2252 CHECK_STATUS(status
, NT_STATUS_OK
);
2253 h
= io
.out
.file
.handle
;
2255 lck
.in
.file
.handle
= h
;
2256 el
[0].flags
= SMB2_LOCK_FLAG_UNLOCK
;
2257 status
= smb2_lock(tree
, &lck
);
2258 CHECK_STATUS(status
, NT_STATUS_OK
);
2261 smb2_util_close(tree
, h
);
2262 smb2_util_unlink(tree
, fname
);
2269 * Open with a RH lease, disconnect, open in another tree, reconnect.
2271 * This test actually demonstrates a minimum level of respect for the durable
2272 * open in the face of another open. As long as this test shows an inability to
2273 * reconnect after an open, the oplock/lease tests above will certainly
2274 * demonstrate an error on reconnect.
2276 static bool test_durable_open_open2_lease(struct torture_context
*tctx
,
2277 struct smb2_tree
*tree1
,
2278 struct smb2_tree
*tree2
)
2280 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
2281 struct smb2_create io1
, io2
;
2282 struct smb2_lease ls
;
2283 struct smb2_handle h1
= {{0}};
2284 struct smb2_handle h2
= {{0}};
2290 struct smbcli_options options
;
2292 options
= tree1
->session
->transport
->options
;
2294 caps
= smb2cli_conn_server_capabilities(tree1
->session
->transport
->conn
);
2295 if (!(caps
& SMB2_CAP_LEASING
)) {
2296 torture_skip(tctx
, "leases are not supported");
2300 * Choose a random name and random lease in case the state is left a
2304 snprintf(fname
, 256, "durable_open_open2_lease_%s.dat",
2305 generate_random_str(tctx
, 8));
2308 smb2_util_unlink(tree1
, fname
);
2310 /* Create with lease */
2311 smb2_lease_create_share(&io1
, &ls
, false /* dir */, fname
,
2312 smb2_util_share_access(""),
2314 smb2_util_lease_state("RH"));
2315 io1
.in
.durable_open
= true;
2317 status
= smb2_create(tree1
, mem_ctx
, &io1
);
2318 CHECK_STATUS(status
, NT_STATUS_OK
);
2319 h1
= io1
.out
.file
.handle
;
2320 CHECK_VAL(io1
.out
.durable_open
, true);
2321 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
2323 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_LEASE
);
2324 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[0], lease
);
2325 CHECK_VAL(io1
.out
.lease_response
.lease_key
.data
[1], ~lease
);
2326 CHECK_VAL(io1
.out
.lease_response
.lease_state
,
2327 smb2_util_lease_state("RH"));
2333 /* Open the file in tree2 */
2334 smb2_oplock_create(&io2
, fname
, SMB2_OPLOCK_LEVEL_NONE
);
2336 status
= smb2_create(tree2
, mem_ctx
, &io2
);
2337 CHECK_STATUS(status
, NT_STATUS_OK
);
2338 h2
= io2
.out
.file
.handle
;
2339 CHECK_CREATED(&io2
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
);
2342 if (!torture_smb2_connection_ext(tctx
, 0, &options
, &tree1
)) {
2343 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2349 io1
.in
.fname
= fname
;
2350 io1
.in
.durable_handle
= &h1
;
2351 io1
.in
.lease_request
= &ls
;
2354 * Windows7 (build 7000) will give away an open immediately if the
2355 * original client is gone. (ZML: This seems like a bug. It should give
2356 * some time for the client to reconnect!)
2358 status
= smb2_create(tree1
, mem_ctx
, &io1
);
2359 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
2360 h1
= io1
.out
.file
.handle
;
2364 smb2_util_close(tree1
, h1
);
2365 smb2_util_unlink(tree1
, fname
);
2369 smb2_util_close(tree2
, h2
);
2370 smb2_util_unlink(tree2
, fname
);
2377 * Open with a batch oplock, disconnect, open in another tree, reconnect.
2379 * This test actually demonstrates a minimum level of respect for the durable
2380 * open in the face of another open. As long as this test shows an inability to
2381 * reconnect after an open, the oplock/lease tests above will certainly
2382 * demonstrate an error on reconnect.
2384 static bool test_durable_open_open2_oplock(struct torture_context
*tctx
,
2385 struct smb2_tree
*tree1
,
2386 struct smb2_tree
*tree2
)
2388 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
2389 struct smb2_create io1
, io2
;
2390 struct smb2_handle h1
= {{0}};
2391 struct smb2_handle h2
= {{0}};
2397 * Choose a random name and random lease in case the state is left a
2400 snprintf(fname
, 256, "durable_open_open2_oplock_%s.dat",
2401 generate_random_str(tctx
, 8));
2404 smb2_util_unlink(tree1
, fname
);
2406 /* Create with batch oplock */
2407 smb2_oplock_create(&io1
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
2408 io1
.in
.durable_open
= true;
2410 status
= smb2_create(tree1
, mem_ctx
, &io1
);
2411 CHECK_STATUS(status
, NT_STATUS_OK
);
2412 h1
= io1
.out
.file
.handle
;
2413 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
2414 CHECK_VAL(io1
.out
.durable_open
, true);
2415 CHECK_VAL(io1
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
2421 /* Open the file in tree2 */
2422 smb2_oplock_create(&io2
, fname
, SMB2_OPLOCK_LEVEL_NONE
);
2424 status
= smb2_create(tree2
, mem_ctx
, &io2
);
2425 CHECK_STATUS(status
, NT_STATUS_OK
);
2426 h2
= io2
.out
.file
.handle
;
2427 CHECK_CREATED(&io1
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
2430 if (!torture_smb2_connection(tctx
, &tree1
)) {
2431 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2437 io1
.in
.fname
= fname
;
2438 io1
.in
.durable_handle
= &h1
;
2440 status
= smb2_create(tree1
, mem_ctx
, &io1
);
2441 CHECK_STATUS(status
, NT_STATUS_OBJECT_NAME_NOT_FOUND
);
2442 h1
= io1
.out
.file
.handle
;
2445 smb2_util_close(tree2
, h2
);
2446 smb2_util_unlink(tree2
, fname
);
2447 if (tree1
!= NULL
) {
2448 smb2_util_close(tree1
, h1
);
2449 smb2_util_unlink(tree1
, fname
);
2459 * test behaviour with initial allocation size
2461 static bool test_durable_open_alloc_size(struct torture_context
*tctx
,
2462 struct smb2_tree
*tree
)
2465 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
2467 struct smb2_handle _h
;
2468 struct smb2_handle
*h
= NULL
;
2469 struct smb2_create io
;
2471 uint64_t previous_session_id
;
2472 uint64_t alloc_size_step
;
2473 uint64_t initial_alloc_size
= 0x100;
2474 const uint8_t *b
= NULL
;
2475 struct smbcli_options options
;
2477 options
= tree
->session
->transport
->options
;
2479 /* Choose a random name in case the state is left a little funky. */
2480 snprintf(fname
, 256, "durable_open_alloc_size_%s.dat",
2481 generate_random_str(tctx
, 8));
2483 smb2_util_unlink(tree
, fname
);
2485 smb2_oplock_create_share(&io
, fname
,
2486 smb2_util_share_access(""),
2487 smb2_util_oplock_level("b"));
2488 io
.in
.durable_open
= true;
2489 io
.in
.alloc_size
= initial_alloc_size
;
2491 status
= smb2_create(tree
, mem_ctx
, &io
);
2492 CHECK_STATUS(status
, NT_STATUS_OK
);
2493 _h
= io
.out
.file
.handle
;
2495 CHECK_NOT_VAL(io
.out
.alloc_size
, 0);
2496 alloc_size_step
= io
.out
.alloc_size
;
2497 CHECK_CREATED_SIZE(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
,
2498 alloc_size_step
, 0);
2499 CHECK_VAL(io
.out
.durable_open
, true);
2500 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
2502 /* prepare buffer */
2503 b
= talloc_zero_size(mem_ctx
, alloc_size_step
);
2506 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
2508 /* disconnect, reconnect and then do durable reopen */
2512 if (!torture_smb2_connection_ext(tctx
, previous_session_id
,
2515 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2521 io
.in
.fname
= fname
;
2522 io
.in
.durable_handle
= h
;
2525 status
= smb2_create(tree
, mem_ctx
, &io
);
2526 CHECK_STATUS(status
, NT_STATUS_OK
);
2527 CHECK_CREATED_SIZE(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
,
2528 alloc_size_step
, 0);
2529 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
2530 _h
= io
.out
.file
.handle
;
2533 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
2535 /* write one byte */
2536 status
= smb2_util_write(tree
, *h
, b
, 0, 1);
2537 CHECK_STATUS(status
, NT_STATUS_OK
);
2539 /* disconnect, reconnect and then do durable reopen */
2543 if (!torture_smb2_connection_ext(tctx
, previous_session_id
,
2546 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2552 io
.in
.fname
= fname
;
2553 io
.in
.durable_handle
= h
;
2556 status
= smb2_create(tree
, mem_ctx
, &io
);
2557 CHECK_STATUS(status
, NT_STATUS_OK
);
2558 CHECK_CREATED_SIZE(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
,
2559 alloc_size_step
, 1);
2560 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
2561 _h
= io
.out
.file
.handle
;
2564 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
2566 /* write more byte than initial allocation size */
2567 status
= smb2_util_write(tree
, *h
, b
, 1, alloc_size_step
);
2569 /* disconnect, reconnect and then do durable reopen */
2573 if (!torture_smb2_connection_ext(tctx
, previous_session_id
,
2576 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2582 io
.in
.fname
= fname
;
2583 io
.in
.durable_handle
= h
;
2586 status
= smb2_create(tree
, mem_ctx
, &io
);
2587 CHECK_STATUS(status
, NT_STATUS_OK
);
2588 CHECK_CREATED_SIZE(&io
, EXISTED
, FILE_ATTRIBUTE_ARCHIVE
,
2589 alloc_size_step
* 2, alloc_size_step
+ 1);
2590 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
2591 _h
= io
.out
.file
.handle
;
2596 smb2_util_close(tree
, *h
);
2599 smb2_util_unlink(tree
, fname
);
2603 talloc_free(mem_ctx
);
2609 * test behaviour when a disconnect happens while creating a read-only file
2611 static bool test_durable_open_read_only(struct torture_context
*tctx
,
2612 struct smb2_tree
*tree
)
2615 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
2617 struct smb2_handle _h
;
2618 struct smb2_handle
*h
= NULL
;
2619 struct smb2_create io
;
2621 uint64_t previous_session_id
;
2622 const uint8_t b
= 0;
2623 uint64_t alloc_size
= 0;
2624 struct smbcli_options options
;
2626 options
= tree
->session
->transport
->options
;
2628 /* Choose a random name in case the state is left a little funky. */
2629 snprintf(fname
, 256, "durable_open_initial_alloc_%s.dat",
2630 generate_random_str(tctx
, 8));
2632 smb2_util_unlink(tree
, fname
);
2634 smb2_oplock_create_share(&io
, fname
,
2635 smb2_util_share_access(""),
2636 smb2_util_oplock_level("b"));
2637 io
.in
.durable_open
= true;
2638 io
.in
.file_attributes
= FILE_ATTRIBUTE_READONLY
;
2640 status
= smb2_create(tree
, mem_ctx
, &io
);
2641 CHECK_STATUS(status
, NT_STATUS_OK
);
2642 _h
= io
.out
.file
.handle
;
2644 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_READONLY
|FILE_ATTRIBUTE_ARCHIVE
);
2645 CHECK_VAL(io
.out
.durable_open
, true);
2646 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
2648 previous_session_id
= smb2cli_session_current_id(tree
->session
->smbXcli
);
2650 /* write one byte */
2651 status
= smb2_util_write(tree
, *h
, &b
, 0, 1);
2652 CHECK_STATUS(status
, NT_STATUS_OK
);
2654 /* disconnect, reconnect and then do durable reopen */
2658 if (!torture_smb2_connection_ext(tctx
, previous_session_id
,
2661 torture_warning(tctx
, "couldn't reconnect, bailing\n");
2667 io
.in
.fname
= fname
;
2668 io
.in
.durable_handle
= h
;
2671 status
= smb2_create(tree
, mem_ctx
, &io
);
2672 CHECK_STATUS(status
, NT_STATUS_OK
);
2673 alloc_size
= io
.out
.alloc_size
;
2674 CHECK_CREATED_SIZE(&io
, EXISTED
,
2675 FILE_ATTRIBUTE_READONLY
|FILE_ATTRIBUTE_ARCHIVE
,
2677 CHECK_VAL(io
.out
.oplock_level
, smb2_util_oplock_level("b"));
2678 _h
= io
.out
.file
.handle
;
2681 /* write one byte */
2682 status
= smb2_util_write(tree
, *h
, &b
, 1, 1);
2683 CHECK_STATUS(status
, NT_STATUS_OK
);
2687 union smb_setfileinfo sfinfo
;
2689 ZERO_STRUCT(sfinfo
);
2690 sfinfo
.basic_info
.level
= RAW_SFILEINFO_BASIC_INFORMATION
;
2691 sfinfo
.basic_info
.in
.file
.handle
= *h
;
2692 sfinfo
.basic_info
.in
.attrib
= FILE_ATTRIBUTE_NORMAL
;
2693 smb2_setinfo_file(tree
, &sfinfo
);
2695 smb2_util_close(tree
, *h
);
2698 smb2_util_unlink(tree
, fname
);
2702 talloc_free(mem_ctx
);
2708 * durable open with oplock, disconnect, exit
2710 static bool test_durable_open_oplock_disconnect(struct torture_context
*tctx
,
2711 struct smb2_tree
*tree
)
2713 TALLOC_CTX
*mem_ctx
= talloc_new(tctx
);
2714 struct smb2_create io
;
2715 struct smb2_handle _h
;
2716 struct smb2_handle
*h
= NULL
;
2721 snprintf(fname
, 256, "durable_open_oplock_disconnect_%s.dat",
2722 generate_random_str(mem_ctx
, 8));
2724 smb2_util_unlink(tree
, fname
);
2726 smb2_oplock_create(&io
, fname
, SMB2_OPLOCK_LEVEL_BATCH
);
2727 io
.in
.durable_open
= true;
2729 status
= smb2_create(tree
, mem_ctx
, &io
);
2730 CHECK_STATUS(status
, NT_STATUS_OK
);
2732 _h
= io
.out
.file
.handle
;
2735 CHECK_CREATED(&io
, CREATED
, FILE_ATTRIBUTE_ARCHIVE
);
2736 CHECK_VAL(io
.out
.durable_open
, true);
2737 CHECK_VAL(io
.out
.oplock_level
, SMB2_OPLOCK_LEVEL_BATCH
);
2746 smb2_util_close(tree
, *h
);
2748 smb2_util_unlink(tree
, fname
);
2750 talloc_free(mem_ctx
);
2755 struct torture_suite
*torture_smb2_durable_open_init(TALLOC_CTX
*ctx
)
2757 struct torture_suite
*suite
=
2758 torture_suite_create(ctx
, "durable-open");
2760 torture_suite_add_1smb2_test(suite
, "open-oplock", test_durable_open_open_oplock
);
2761 torture_suite_add_1smb2_test(suite
, "open-lease", test_durable_open_open_lease
);
2762 torture_suite_add_1smb2_test(suite
, "reopen1", test_durable_open_reopen1
);
2763 torture_suite_add_1smb2_test(suite
, "reopen1a", test_durable_open_reopen1a
);
2764 torture_suite_add_1smb2_test(suite
, "reopen1a-lease", test_durable_open_reopen1a_lease
);
2765 torture_suite_add_1smb2_test(suite
, "reopen2", test_durable_open_reopen2
);
2766 torture_suite_add_1smb2_test(suite
, "reopen2-lease", test_durable_open_reopen2_lease
);
2767 torture_suite_add_1smb2_test(suite
, "reopen2-lease-v2", test_durable_open_reopen2_lease_v2
);
2768 torture_suite_add_1smb2_test(suite
, "reopen2a", test_durable_open_reopen2a
);
2769 torture_suite_add_1smb2_test(suite
, "reopen3", test_durable_open_reopen3
);
2770 torture_suite_add_1smb2_test(suite
, "reopen4", test_durable_open_reopen4
);
2771 torture_suite_add_1smb2_test(suite
, "delete_on_close1",
2772 test_durable_open_delete_on_close1
);
2773 torture_suite_add_1smb2_test(suite
, "delete_on_close2",
2774 test_durable_open_delete_on_close2
);
2775 torture_suite_add_1smb2_test(suite
, "file-position",
2776 test_durable_open_file_position
);
2777 torture_suite_add_2smb2_test(suite
, "oplock", test_durable_open_oplock
);
2778 torture_suite_add_2smb2_test(suite
, "lease", test_durable_open_lease
);
2779 torture_suite_add_1smb2_test(suite
, "lock-oplock", test_durable_open_lock_oplock
);
2780 torture_suite_add_1smb2_test(suite
, "lock-lease", test_durable_open_lock_lease
);
2781 torture_suite_add_2smb2_test(suite
, "open2-lease",
2782 test_durable_open_open2_lease
);
2783 torture_suite_add_2smb2_test(suite
, "open2-oplock",
2784 test_durable_open_open2_oplock
);
2785 torture_suite_add_1smb2_test(suite
, "alloc-size",
2786 test_durable_open_alloc_size
);
2787 torture_suite_add_1smb2_test(suite
, "read-only",
2788 test_durable_open_read_only
);
2790 suite
->description
= talloc_strdup(suite
, "SMB2-DURABLE-OPEN tests");
2795 struct torture_suite
*torture_smb2_durable_open_disconnect_init(TALLOC_CTX
*ctx
)
2797 struct torture_suite
*suite
=
2798 torture_suite_create(ctx
,
2799 "durable-open-disconnect");
2801 torture_suite_add_1smb2_test(suite
, "open-oplock-disconnect",
2802 test_durable_open_oplock_disconnect
);
2804 suite
->description
= talloc_strdup(suite
,
2805 "SMB2-DURABLE-OPEN-DISCONNECT tests");