s4:torture:smb2: add new durable-open.reopen1a test
[Samba.git] / source4 / torture / smb2 / durable_open.c
blob23425c8e44dade92257adf2126f5afa3b8d4f24b
1 /*
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/>.
23 #include "includes.h"
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); \
35 ret = false; \
36 }} while (0)
38 #define CHECK_NOT_VAL(v, correct) do { \
39 if ((v) == (correct)) { \
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)correct); \
42 ret = false; \
43 }} while (0)
45 #define CHECK_STATUS(status, correct) do { \
46 if (!NT_STATUS_EQUAL(status, correct)) { \
47 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
48 nt_errstr(status), nt_errstr(correct)); \
49 ret = false; \
50 goto done; \
51 }} while (0)
53 #define CHECK_CREATED(__io, __created, __attribute) \
54 do { \
55 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
56 CHECK_VAL((__io)->out.alloc_size, 0); \
57 CHECK_VAL((__io)->out.size, 0); \
58 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
59 CHECK_VAL((__io)->out.reserved2, 0); \
60 } while(0)
62 #define CHECK_CREATED_SIZE(__io, __created, __attribute, __alloc_size, __size) \
63 do { \
64 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
65 CHECK_VAL((__io)->out.alloc_size, (__alloc_size)); \
66 CHECK_VAL((__io)->out.size, (__size)); \
67 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
68 CHECK_VAL((__io)->out.reserved2, 0); \
69 } while(0)
73 /**
74 * basic durable_open test.
75 * durable state should only be granted when requested
76 * along with a batch oplock or a handle lease.
78 * This test tests durable open with all possible oplock types.
81 struct durable_open_vs_oplock {
82 const char *level;
83 const char *share_mode;
84 bool expected;
87 #define NUM_OPLOCK_TYPES 4
88 #define NUM_SHARE_MODES 8
89 #define NUM_OPLOCK_OPEN_TESTS ( NUM_OPLOCK_TYPES * NUM_SHARE_MODES )
90 static struct durable_open_vs_oplock durable_open_vs_oplock_table[NUM_OPLOCK_OPEN_TESTS] =
92 { "", "", false },
93 { "", "R", false },
94 { "", "W", false },
95 { "", "D", false },
96 { "", "RD", false },
97 { "", "RW", false },
98 { "", "WD", false },
99 { "", "RWD", false },
101 { "s", "", false },
102 { "s", "R", false },
103 { "s", "W", false },
104 { "s", "D", false },
105 { "s", "RD", false },
106 { "s", "RW", false },
107 { "s", "WD", false },
108 { "s", "RWD", false },
110 { "x", "", false },
111 { "x", "R", false },
112 { "x", "W", false },
113 { "x", "D", false },
114 { "x", "RD", false },
115 { "x", "RW", false },
116 { "x", "WD", false },
117 { "x", "RWD", false },
119 { "b", "", true },
120 { "b", "R", true },
121 { "b", "W", true },
122 { "b", "D", true },
123 { "b", "RD", true },
124 { "b", "RW", true },
125 { "b", "WD", true },
126 { "b", "RWD", true },
129 static bool test_one_durable_open_open_oplock(struct torture_context *tctx,
130 struct smb2_tree *tree,
131 const char *fname,
132 struct durable_open_vs_oplock test)
134 NTSTATUS status;
135 TALLOC_CTX *mem_ctx = talloc_new(tctx);
136 struct smb2_handle _h;
137 struct smb2_handle *h = NULL;
138 bool ret = true;
139 struct smb2_create io;
141 smb2_util_unlink(tree, fname);
143 smb2_oplock_create_share(&io, fname,
144 smb2_util_share_access(test.share_mode),
145 smb2_util_oplock_level(test.level));
146 io.in.durable_open = true;
148 status = smb2_create(tree, mem_ctx, &io);
149 CHECK_STATUS(status, NT_STATUS_OK);
150 _h = io.out.file.handle;
151 h = &_h;
152 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
153 CHECK_VAL(io.out.durable_open, test.expected);
154 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level(test.level));
156 done:
157 if (h != NULL) {
158 smb2_util_close(tree, *h);
160 smb2_util_unlink(tree, fname);
161 talloc_free(mem_ctx);
163 return ret;
166 static bool test_durable_open_open_oplock(struct torture_context *tctx,
167 struct smb2_tree *tree)
169 TALLOC_CTX *mem_ctx = talloc_new(tctx);
170 char fname[256];
171 bool ret = true;
172 int i;
174 /* Choose a random name in case the state is left a little funky. */
175 snprintf(fname, 256, "durable_open_open_oplock_%s.dat", generate_random_str(tctx, 8));
177 smb2_util_unlink(tree, fname);
179 /* test various oplock levels with durable open */
181 for (i = 0; i < NUM_OPLOCK_OPEN_TESTS; i++) {
182 ret = test_one_durable_open_open_oplock(tctx,
183 tree,
184 fname,
185 durable_open_vs_oplock_table[i]);
186 if (ret == false) {
187 goto done;
191 done:
192 smb2_util_unlink(tree, fname);
193 talloc_free(tree);
194 talloc_free(mem_ctx);
196 return ret;
200 * basic durable_open test.
201 * durable state should only be granted when requested
202 * along with a batch oplock or a handle lease.
204 * This test tests durable open with all valid lease types.
207 struct durable_open_vs_lease {
208 const char *type;
209 const char *share_mode;
210 bool expected;
213 #define NUM_LEASE_TYPES 5
214 #define NUM_LEASE_OPEN_TESTS ( NUM_LEASE_TYPES * NUM_SHARE_MODES )
215 static struct durable_open_vs_lease durable_open_vs_lease_table[NUM_LEASE_OPEN_TESTS] =
217 { "", "", false },
218 { "", "R", false },
219 { "", "W", false },
220 { "", "D", false },
221 { "", "RW", false },
222 { "", "RD", false },
223 { "", "WD", false },
224 { "", "RWD", false },
226 { "R", "", false },
227 { "R", "R", false },
228 { "R", "W", false },
229 { "R", "D", false },
230 { "R", "RW", false },
231 { "R", "RD", false },
232 { "R", "DW", false },
233 { "R", "RWD", false },
235 { "RW", "", false },
236 { "RW", "R", false },
237 { "RW", "W", false },
238 { "RW", "D", false },
239 { "RW", "RW", false },
240 { "RW", "RD", false },
241 { "RW", "WD", false },
242 { "RW", "RWD", false },
244 { "RH", "", true },
245 { "RH", "R", true },
246 { "RH", "W", true },
247 { "RH", "D", true },
248 { "RH", "RW", true },
249 { "RH", "RD", true },
250 { "RH", "WD", true },
251 { "RH", "RWD", true },
253 { "RHW", "", true },
254 { "RHW", "R", true },
255 { "RHW", "W", true },
256 { "RHW", "D", true },
257 { "RHW", "RW", true },
258 { "RHW", "RD", true },
259 { "RHW", "WD", true },
260 { "RHW", "RWD", true },
263 static bool test_one_durable_open_open_lease(struct torture_context *tctx,
264 struct smb2_tree *tree,
265 const char *fname,
266 struct durable_open_vs_lease test)
268 NTSTATUS status;
269 TALLOC_CTX *mem_ctx = talloc_new(tctx);
270 struct smb2_handle _h;
271 struct smb2_handle *h = NULL;
272 bool ret = true;
273 struct smb2_create io;
274 struct smb2_lease ls;
275 uint64_t lease;
276 uint32_t caps;
278 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
279 if (!(caps & SMB2_CAP_LEASING)) {
280 torture_skip(tctx, "leases are not supported");
283 smb2_util_unlink(tree, fname);
285 lease = random();
287 smb2_lease_create_share(&io, &ls, false /* dir */, fname,
288 smb2_util_share_access(test.share_mode),
289 lease,
290 smb2_util_lease_state(test.type));
291 io.in.durable_open = true;
293 status = smb2_create(tree, mem_ctx, &io);
294 CHECK_STATUS(status, NT_STATUS_OK);
295 _h = io.out.file.handle;
296 h = &_h;
297 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
298 CHECK_VAL(io.out.durable_open, test.expected);
299 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
300 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
301 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
302 CHECK_VAL(io.out.lease_response.lease_state,
303 smb2_util_lease_state(test.type));
304 done:
305 if (h != NULL) {
306 smb2_util_close(tree, *h);
308 smb2_util_unlink(tree, fname);
309 talloc_free(mem_ctx);
311 return ret;
314 static bool test_durable_open_open_lease(struct torture_context *tctx,
315 struct smb2_tree *tree)
317 TALLOC_CTX *mem_ctx = talloc_new(tctx);
318 char fname[256];
319 bool ret = true;
320 int i;
321 uint32_t caps;
323 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
324 if (!(caps & SMB2_CAP_LEASING)) {
325 torture_skip(tctx, "leases are not supported");
328 /* Choose a random name in case the state is left a little funky. */
329 snprintf(fname, 256, "durable_open_open_lease_%s.dat", generate_random_str(tctx, 8));
331 smb2_util_unlink(tree, fname);
334 /* test various oplock levels with durable open */
336 for (i = 0; i < NUM_LEASE_OPEN_TESTS; i++) {
337 ret = test_one_durable_open_open_lease(tctx,
338 tree,
339 fname,
340 durable_open_vs_lease_table[i]);
341 if (ret == false) {
342 goto done;
346 done:
347 smb2_util_unlink(tree, fname);
348 talloc_free(tree);
349 talloc_free(mem_ctx);
351 return ret;
355 * basic test for doing a durable open
356 * and do a durable reopen on the same connection
357 * while the first open is still active (fails)
359 static bool test_durable_open_reopen1(struct torture_context *tctx,
360 struct smb2_tree *tree)
362 NTSTATUS status;
363 TALLOC_CTX *mem_ctx = talloc_new(tctx);
364 char fname[256];
365 struct smb2_handle _h;
366 struct smb2_handle *h = NULL;
367 struct smb2_create io1, io2;
368 bool ret = true;
370 /* Choose a random name in case the state is left a little funky. */
371 snprintf(fname, 256, "durable_open_reopen1_%s.dat",
372 generate_random_str(tctx, 8));
374 smb2_util_unlink(tree, fname);
376 smb2_oplock_create_share(&io1, fname,
377 smb2_util_share_access(""),
378 smb2_util_oplock_level("b"));
379 io1.in.durable_open = true;
381 status = smb2_create(tree, mem_ctx, &io1);
382 CHECK_STATUS(status, NT_STATUS_OK);
383 _h = io1.out.file.handle;
384 h = &_h;
385 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
386 CHECK_VAL(io1.out.durable_open, true);
387 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
389 /* try a durable reconnect while the file is still open */
390 ZERO_STRUCT(io2);
391 io2.in.fname = fname;
392 io2.in.durable_handle = h;
394 status = smb2_create(tree, mem_ctx, &io2);
395 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
397 done:
398 if (h != NULL) {
399 smb2_util_close(tree, *h);
402 smb2_util_unlink(tree, fname);
404 talloc_free(tree);
406 talloc_free(mem_ctx);
408 return ret;
412 * Basic test for doing a durable open
413 * and do a session reconnect while the first
414 * session is still active and the handle is
415 * still open in the client.
416 * This closes the original session and a
417 * durable reconnect on the new session succeeds.
419 static bool test_durable_open_reopen1a(struct torture_context *tctx,
420 struct smb2_tree *tree)
422 NTSTATUS status;
423 TALLOC_CTX *mem_ctx = talloc_new(tctx);
424 char fname[256];
425 struct smb2_handle _h;
426 struct smb2_handle *h = NULL;
427 struct smb2_create io1, io2;
428 bool ret = true;
429 struct smb2_tree *tree2 = NULL;
430 uint64_t previous_session_id;
431 struct smbcli_options options;
433 options = tree->session->transport->options;
435 /* Choose a random name in case the state is left a little funky. */
436 snprintf(fname, 256, "durable_open_reopen1a_%s.dat",
437 generate_random_str(tctx, 8));
439 smb2_util_unlink(tree, fname);
441 smb2_oplock_create_share(&io1, fname,
442 smb2_util_share_access(""),
443 smb2_util_oplock_level("b"));
444 io1.in.durable_open = true;
446 status = smb2_create(tree, mem_ctx, &io1);
447 CHECK_STATUS(status, NT_STATUS_OK);
448 _h = io1.out.file.handle;
449 h = &_h;
450 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
451 CHECK_VAL(io1.out.durable_open, true);
452 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
455 * a session reconnect on a second tcp connection
458 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
460 if (!torture_smb2_connection_ext(tctx, previous_session_id,
461 &options, &tree2))
463 torture_warning(tctx, "couldn't reconnect, bailing\n");
464 ret = false;
465 goto done;
469 * check that this has deleted the old session
472 ZERO_STRUCT(io2);
473 io2.in.fname = fname;
474 io2.in.durable_handle = h;
476 status = smb2_create(tree, mem_ctx, &io2);
477 CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED);
480 * but a durable reconnect on the new session succeeds:
483 ZERO_STRUCT(io2);
484 io2.in.fname = fname;
485 io2.in.durable_handle = h;
487 status = smb2_create(tree2, mem_ctx, &io2);
488 CHECK_STATUS(status, NT_STATUS_OK);
489 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
490 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
491 _h = io2.out.file.handle;
492 h = &_h;
494 done:
495 if (h != NULL) {
496 smb2_util_close(tree2, *h);
499 smb2_util_unlink(tree2, fname);
501 talloc_free(tree2);
503 talloc_free(tree);
505 talloc_free(mem_ctx);
507 return ret;
511 * basic test for doing a durable open
512 * tcp disconnect, reconnect, do a durable reopen (succeeds)
514 static bool test_durable_open_reopen2(struct torture_context *tctx,
515 struct smb2_tree *tree)
517 NTSTATUS status;
518 TALLOC_CTX *mem_ctx = talloc_new(tctx);
519 char fname[256];
520 struct smb2_handle _h;
521 struct smb2_handle *h = NULL;
522 struct smb2_create io;
523 bool ret = true;
525 /* Choose a random name in case the state is left a little funky. */
526 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
527 generate_random_str(tctx, 8));
529 smb2_util_unlink(tree, fname);
531 smb2_oplock_create_share(&io, fname,
532 smb2_util_share_access(""),
533 smb2_util_oplock_level("b"));
534 io.in.durable_open = true;
536 status = smb2_create(tree, mem_ctx, &io);
537 CHECK_STATUS(status, NT_STATUS_OK);
538 _h = io.out.file.handle;
539 h = &_h;
540 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
541 CHECK_VAL(io.out.durable_open, true);
542 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
544 /* disconnect, leaving the durable in place */
545 TALLOC_FREE(tree);
547 if (!torture_smb2_connection(tctx, &tree)) {
548 torture_warning(tctx, "couldn't reconnect, bailing\n");
549 ret = false;
550 goto done;
553 ZERO_STRUCT(io);
554 /* the path name is ignored by the server */
555 io.in.fname = fname;
556 io.in.durable_handle = h; /* durable v1 reconnect request */
557 h = NULL;
559 status = smb2_create(tree, mem_ctx, &io);
560 CHECK_STATUS(status, NT_STATUS_OK);
561 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
562 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
563 _h = io.out.file.handle;
564 h = &_h;
566 /* disconnect again, leaving the durable in place */
567 TALLOC_FREE(tree);
569 if (!torture_smb2_connection(tctx, &tree)) {
570 torture_warning(tctx, "couldn't reconnect, bailing\n");
571 ret = false;
572 goto done;
576 * show that the filename and many other fields
577 * are ignored. only the reconnect request blob
578 * is important.
580 ZERO_STRUCT(io);
581 /* the path name is 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__";
593 io.in.durable_handle = h; /* durable v1 reconnect request */
594 h = NULL;
596 status = smb2_create(tree, mem_ctx, &io);
597 CHECK_STATUS(status, NT_STATUS_OK);
598 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
599 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
600 _h = io.out.file.handle;
601 h = &_h;
603 /* disconnect, leaving the durable in place */
604 TALLOC_FREE(tree);
606 if (!torture_smb2_connection(tctx, &tree)) {
607 torture_warning(tctx, "couldn't reconnect, bailing\n");
608 ret = false;
609 goto done;
613 * show that an additionally specified durable v1 request
614 * is ignored by the server.
615 * See MS-SMB2, 3.3.5.9.7
616 * Handling the SMB2_CREATE_DURABLE_HANDLE_RECONNECT Create Context
618 ZERO_STRUCT(io);
619 /* the path name is ignored by the server */
620 io.in.fname = fname;
621 io.in.durable_handle = h; /* durable v1 reconnect request */
622 io.in.durable_open = true; /* durable v1 handle request */
623 h = NULL;
625 status = smb2_create(tree, mem_ctx, &io);
626 CHECK_STATUS(status, NT_STATUS_OK);
627 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
628 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
629 _h = io.out.file.handle;
630 h = &_h;
632 done:
633 if (tree != NULL) {
634 if (h != NULL) {
635 smb2_util_close(tree, *h);
638 smb2_util_unlink(tree, fname);
640 talloc_free(tree);
643 talloc_free(mem_ctx);
645 return ret;
649 * lease variant of reopen2
650 * basic test for doing a durable open
651 * tcp disconnect, reconnect, do a durable reopen (succeeds)
653 static bool test_durable_open_reopen2_lease(struct torture_context *tctx,
654 struct smb2_tree *tree)
656 NTSTATUS status;
657 TALLOC_CTX *mem_ctx = talloc_new(tctx);
658 char fname[256];
659 struct smb2_handle _h;
660 struct smb2_handle *h = NULL;
661 struct smb2_create io;
662 struct smb2_lease ls;
663 uint64_t lease_key;
664 bool ret = true;
665 struct smbcli_options options;
666 uint32_t caps;
668 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
669 if (!(caps & SMB2_CAP_LEASING)) {
670 torture_skip(tctx, "leases are not supported");
673 options = tree->session->transport->options;
675 /* Choose a random name in case the state is left a little funky. */
676 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
677 generate_random_str(tctx, 8));
679 smb2_util_unlink(tree, fname);
681 lease_key = random();
682 smb2_lease_create(&io, &ls, false /* dir */, fname, lease_key,
683 smb2_util_lease_state("RWH"));
684 io.in.durable_open = true;
686 status = smb2_create(tree, mem_ctx, &io);
687 CHECK_STATUS(status, NT_STATUS_OK);
688 _h = io.out.file.handle;
689 h = &_h;
690 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
692 CHECK_VAL(io.out.durable_open, true);
693 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
694 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
695 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
696 CHECK_VAL(io.out.lease_response.lease_state,
697 smb2_util_lease_state("RWH"));
698 CHECK_VAL(io.out.lease_response.lease_flags, 0);
699 CHECK_VAL(io.out.lease_response.lease_duration, 0);
701 /* disconnect, reconnect and then do durable reopen */
702 TALLOC_FREE(tree);
704 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
705 torture_warning(tctx, "couldn't reconnect, bailing\n");
706 ret = false;
707 goto done;
711 /* a few failure tests: */
714 * several attempts without lease attached:
715 * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
716 * irrespective of file name provided
719 ZERO_STRUCT(io);
720 io.in.fname = "";
721 io.in.durable_handle = h;
722 status = smb2_create(tree, mem_ctx, &io);
723 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
725 ZERO_STRUCT(io);
726 io.in.fname = "__non_existing_fname__";
727 io.in.durable_handle = h;
728 status = smb2_create(tree, mem_ctx, &io);
729 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
731 ZERO_STRUCT(io);
732 io.in.fname = fname;
733 io.in.durable_handle = h;
734 status = smb2_create(tree, mem_ctx, &io);
735 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
738 * attempt with lease provided, but
739 * with a changed lease key. => fails
741 ZERO_STRUCT(io);
742 io.in.fname = fname;
743 io.in.durable_open = false;
744 io.in.durable_handle = h;
745 io.in.lease_request = &ls;
746 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
747 /* a wrong lease key lets the request fail */
748 ls.lease_key.data[0]++;
750 status = smb2_create(tree, mem_ctx, &io);
751 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
753 /* restore the correct lease key */
754 ls.lease_key.data[0]--;
757 * this last failing attempt is almost correct:
758 * only problem is: we use the wrong filename...
759 * Note that this gives INVALID_PARAMETER.
760 * This is different from oplocks!
762 ZERO_STRUCT(io);
763 io.in.fname = "__non_existing_fname__";
764 io.in.durable_open = false;
765 io.in.durable_handle = h;
766 io.in.lease_request = &ls;
767 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
769 status = smb2_create(tree, mem_ctx, &io);
770 CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);
773 * Now for a succeeding reconnect:
776 ZERO_STRUCT(io);
777 io.in.fname = fname;
778 io.in.durable_open = false;
779 io.in.durable_handle = h;
780 io.in.lease_request = &ls;
781 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
783 /* the requested lease state is irrelevant */
784 ls.lease_state = smb2_util_lease_state("");
786 h = NULL;
788 status = smb2_create(tree, mem_ctx, &io);
789 CHECK_STATUS(status, NT_STATUS_OK);
791 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
792 CHECK_VAL(io.out.durable_open, false);
793 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
794 CHECK_VAL(io.out.persistent_open, false);
795 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
796 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
797 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
798 CHECK_VAL(io.out.lease_response.lease_state,
799 smb2_util_lease_state("RWH"));
800 CHECK_VAL(io.out.lease_response.lease_flags, 0);
801 CHECK_VAL(io.out.lease_response.lease_duration, 0);
802 _h = io.out.file.handle;
803 h = &_h;
805 /* disconnect one more time */
806 TALLOC_FREE(tree);
808 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
809 torture_warning(tctx, "couldn't reconnect, bailing\n");
810 ret = false;
811 goto done;
815 * demonstrate that various parameters are ignored
816 * in the reconnect
819 ZERO_STRUCT(io);
821 * These are completely ignored by the server
823 io.in.security_flags = 0x78;
824 io.in.oplock_level = 0x78;
825 io.in.impersonation_level = 0x12345678;
826 io.in.create_flags = 0x12345678;
827 io.in.reserved = 0x12345678;
828 io.in.desired_access = 0x12345678;
829 io.in.file_attributes = 0x12345678;
830 io.in.share_access = 0x12345678;
831 io.in.create_disposition = 0x12345678;
832 io.in.create_options = 0x12345678;
835 * only these are checked:
836 * - io.in.fname
837 * - io.in.durable_handle,
838 * - io.in.lease_request->lease_key
841 io.in.fname = fname;
842 io.in.durable_open_v2 = false;
843 io.in.durable_handle_v2 = h;
844 io.in.lease_request = &ls;
846 /* the requested lease state is irrelevant */
847 ls.lease_state = smb2_util_lease_state("");
849 h = NULL;
851 status = smb2_create(tree, mem_ctx, &io);
852 CHECK_STATUS(status, NT_STATUS_OK);
854 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
855 CHECK_VAL(io.out.durable_open, false);
856 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
857 CHECK_VAL(io.out.persistent_open, false);
858 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
859 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease_key);
860 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease_key);
861 CHECK_VAL(io.out.lease_response.lease_state,
862 smb2_util_lease_state("RWH"));
863 CHECK_VAL(io.out.lease_response.lease_flags, 0);
864 CHECK_VAL(io.out.lease_response.lease_duration, 0);
866 _h = io.out.file.handle;
867 h = &_h;
869 done:
870 if (tree != NULL) {
871 if (h != NULL) {
872 smb2_util_close(tree, *h);
875 smb2_util_unlink(tree, fname);
877 talloc_free(tree);
880 talloc_free(mem_ctx);
882 return ret;
886 * lease v2 variant of reopen2
887 * basic test for doing a durable open
888 * tcp disconnect, reconnect, do a durable reopen (succeeds)
890 static bool test_durable_open_reopen2_lease_v2(struct torture_context *tctx,
891 struct smb2_tree *tree)
893 NTSTATUS status;
894 TALLOC_CTX *mem_ctx = talloc_new(tctx);
895 char fname[256];
896 struct smb2_handle _h;
897 struct smb2_handle *h = NULL;
898 struct smb2_create io;
899 struct smb2_lease ls;
900 uint64_t lease_key;
901 bool ret = true;
902 struct smbcli_options options;
903 uint32_t caps;
905 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
906 if (!(caps & SMB2_CAP_LEASING)) {
907 torture_skip(tctx, "leases are not supported");
910 options = tree->session->transport->options;
912 /* Choose a random name in case the state is left a little funky. */
913 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
914 generate_random_str(tctx, 8));
916 smb2_util_unlink(tree, fname);
918 lease_key = random();
919 smb2_lease_v2_create(&io, &ls, false /* dir */, fname,
920 lease_key, 0, /* parent lease key */
921 smb2_util_lease_state("RWH"), 0 /* lease epoch */);
922 io.in.durable_open = true;
924 status = smb2_create(tree, mem_ctx, &io);
925 CHECK_STATUS(status, NT_STATUS_OK);
926 _h = io.out.file.handle;
927 h = &_h;
928 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
930 CHECK_VAL(io.out.durable_open, true);
931 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
932 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
933 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
934 CHECK_VAL(io.out.lease_response_v2.lease_state,
935 smb2_util_lease_state("RWH"));
936 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
937 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
939 /* disconnect, reconnect and then do durable reopen */
940 TALLOC_FREE(tree);
942 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
943 torture_warning(tctx, "couldn't reconnect, bailing\n");
944 ret = false;
945 goto done;
948 /* a few failure tests: */
951 * several attempts without lease attached:
952 * all fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
953 * irrespective of file name provided
956 ZERO_STRUCT(io);
957 io.in.fname = "";
958 io.in.durable_handle = h;
959 status = smb2_create(tree, mem_ctx, &io);
960 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
962 ZERO_STRUCT(io);
963 io.in.fname = "__non_existing_fname__";
964 io.in.durable_handle = h;
965 status = smb2_create(tree, mem_ctx, &io);
966 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
968 ZERO_STRUCT(io);
969 io.in.fname = fname;
970 io.in.durable_handle = h;
971 status = smb2_create(tree, mem_ctx, &io);
972 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
975 * attempt with lease provided, but
976 * with a changed lease key. => fails
978 ZERO_STRUCT(io);
979 io.in.fname = fname;
980 io.in.durable_open = false;
981 io.in.durable_handle = h;
982 io.in.lease_request_v2 = &ls;
983 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
984 /* a wrong lease key lets the request fail */
985 ls.lease_key.data[0]++;
987 status = smb2_create(tree, mem_ctx, &io);
988 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
990 /* restore the correct lease key */
991 ls.lease_key.data[0]--;
994 * this last failing attempt is almost correct:
995 * only problem is: we use the wrong filename...
996 * Note that this gives INVALID_PARAMETER.
997 * This is different from oplocks!
999 ZERO_STRUCT(io);
1000 io.in.fname = "__non_existing_fname__";
1001 io.in.durable_open = false;
1002 io.in.durable_handle = h;
1003 io.in.lease_request_v2 = &ls;
1004 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1006 status = smb2_create(tree, mem_ctx, &io);
1007 CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);
1010 * Now for a succeeding reconnect:
1013 ZERO_STRUCT(io);
1014 io.in.fname = fname;
1015 io.in.durable_open = false;
1016 io.in.durable_handle = h;
1017 io.in.lease_request_v2 = &ls;
1018 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
1020 /* the requested lease state is irrelevant */
1021 ls.lease_state = smb2_util_lease_state("");
1023 h = NULL;
1025 status = smb2_create(tree, mem_ctx, &io);
1026 CHECK_STATUS(status, NT_STATUS_OK);
1028 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1029 CHECK_VAL(io.out.durable_open, false);
1030 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1031 CHECK_VAL(io.out.persistent_open, false);
1032 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1033 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
1034 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
1035 CHECK_VAL(io.out.lease_response_v2.lease_state,
1036 smb2_util_lease_state("RWH"));
1037 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
1038 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
1039 _h = io.out.file.handle;
1040 h = &_h;
1042 /* disconnect one more time */
1043 TALLOC_FREE(tree);
1045 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
1046 torture_warning(tctx, "couldn't reconnect, bailing\n");
1047 ret = false;
1048 goto done;
1052 * demonstrate that various parameters are ignored
1053 * in the reconnect
1056 ZERO_STRUCT(io);
1058 * These are completely ignored by the server
1060 io.in.security_flags = 0x78;
1061 io.in.oplock_level = 0x78;
1062 io.in.impersonation_level = 0x12345678;
1063 io.in.create_flags = 0x12345678;
1064 io.in.reserved = 0x12345678;
1065 io.in.desired_access = 0x12345678;
1066 io.in.file_attributes = 0x12345678;
1067 io.in.share_access = 0x12345678;
1068 io.in.create_disposition = 0x12345678;
1069 io.in.create_options = 0x12345678;
1072 * only these are checked:
1073 * - io.in.fname
1074 * - io.in.durable_handle,
1075 * - io.in.lease_request->lease_key
1078 io.in.fname = fname;
1079 io.in.durable_open_v2 = false;
1080 io.in.durable_handle_v2 = h;
1081 io.in.lease_request_v2 = &ls;
1083 /* the requested lease state is irrelevant */
1084 ls.lease_state = smb2_util_lease_state("");
1086 h = NULL;
1088 status = smb2_create(tree, mem_ctx, &io);
1089 CHECK_STATUS(status, NT_STATUS_OK);
1091 CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1092 CHECK_VAL(io.out.durable_open, false);
1093 CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */
1094 CHECK_VAL(io.out.persistent_open, false);
1095 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1096 CHECK_VAL(io.out.lease_response_v2.lease_key.data[0], lease_key);
1097 CHECK_VAL(io.out.lease_response_v2.lease_key.data[1], ~lease_key);
1098 CHECK_VAL(io.out.lease_response_v2.lease_state,
1099 smb2_util_lease_state("RWH"));
1100 CHECK_VAL(io.out.lease_response_v2.lease_flags, 0);
1101 CHECK_VAL(io.out.lease_response_v2.lease_duration, 0);
1103 _h = io.out.file.handle;
1104 h = &_h;
1106 done:
1107 if (tree != NULL) {
1108 if (h != NULL) {
1109 smb2_util_close(tree, *h);
1112 smb2_util_unlink(tree, fname);
1114 talloc_free(tree);
1117 talloc_free(mem_ctx);
1119 return ret;
1123 * basic test for doing a durable open
1124 * tcp disconnect, reconnect with a session reconnect and
1125 * do a durable reopen (succeeds)
1127 static bool test_durable_open_reopen2a(struct torture_context *tctx,
1128 struct smb2_tree *tree)
1130 NTSTATUS status;
1131 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1132 char fname[256];
1133 struct smb2_handle _h;
1134 struct smb2_handle *h = NULL;
1135 struct smb2_create io1, io2;
1136 uint64_t previous_session_id;
1137 bool ret = true;
1138 struct smbcli_options options;
1140 options = tree->session->transport->options;
1142 /* Choose a random name in case the state is left a little funky. */
1143 snprintf(fname, 256, "durable_open_reopen2_%s.dat",
1144 generate_random_str(tctx, 8));
1146 smb2_util_unlink(tree, fname);
1148 smb2_oplock_create_share(&io1, fname,
1149 smb2_util_share_access(""),
1150 smb2_util_oplock_level("b"));
1151 io1.in.durable_open = true;
1153 status = smb2_create(tree, mem_ctx, &io1);
1154 CHECK_STATUS(status, NT_STATUS_OK);
1155 _h = io1.out.file.handle;
1156 h = &_h;
1157 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1158 CHECK_VAL(io1.out.durable_open, true);
1159 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1161 /* disconnect, reconnect and then do durable reopen */
1162 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
1163 talloc_free(tree);
1164 tree = NULL;
1166 if (!torture_smb2_connection_ext(tctx, previous_session_id,
1167 &options, &tree))
1169 torture_warning(tctx, "couldn't reconnect, bailing\n");
1170 ret = false;
1171 goto done;
1174 ZERO_STRUCT(io2);
1175 io2.in.fname = fname;
1176 io2.in.durable_handle = h;
1177 h = NULL;
1179 status = smb2_create(tree, mem_ctx, &io2);
1180 CHECK_STATUS(status, NT_STATUS_OK);
1181 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1182 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
1183 _h = io2.out.file.handle;
1184 h = &_h;
1186 done:
1187 if (tree != NULL) {
1188 if (h != NULL) {
1189 smb2_util_close(tree, *h);
1192 smb2_util_unlink(tree, fname);
1194 talloc_free(tree);
1197 talloc_free(mem_ctx);
1199 return ret;
1204 * basic test for doing a durable open:
1205 * tdis, new tcon, try durable reopen (fails)
1207 static bool test_durable_open_reopen3(struct torture_context *tctx,
1208 struct smb2_tree *tree)
1210 NTSTATUS status;
1211 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1212 char fname[256];
1213 struct smb2_handle _h;
1214 struct smb2_handle *h = NULL;
1215 struct smb2_create io1, io2;
1216 bool ret = true;
1217 struct smb2_tree *tree2;
1219 /* Choose a random name in case the state is left a little funky. */
1220 snprintf(fname, 256, "durable_open_reopen3_%s.dat",
1221 generate_random_str(tctx, 8));
1223 smb2_util_unlink(tree, fname);
1225 smb2_oplock_create_share(&io1, fname,
1226 smb2_util_share_access(""),
1227 smb2_util_oplock_level("b"));
1228 io1.in.durable_open = true;
1230 status = smb2_create(tree, mem_ctx, &io1);
1231 CHECK_STATUS(status, NT_STATUS_OK);
1232 _h = io1.out.file.handle;
1233 h = &_h;
1234 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1235 CHECK_VAL(io1.out.durable_open, true);
1236 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1238 /* disconnect, reconnect and then do durable reopen */
1239 status = smb2_tdis(tree);
1240 CHECK_STATUS(status, NT_STATUS_OK);
1242 if (!torture_smb2_tree_connect(tctx, tree->session, mem_ctx, &tree2)) {
1243 torture_warning(tctx, "couldn't reconnect to share, bailing\n");
1244 ret = false;
1245 goto done;
1249 ZERO_STRUCT(io2);
1250 io2.in.fname = fname;
1251 io2.in.durable_handle = h;
1253 status = smb2_create(tree2, mem_ctx, &io2);
1254 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1256 done:
1257 if (tree != NULL) {
1258 if (h != NULL) {
1259 smb2_util_close(tree, *h);
1262 smb2_util_unlink(tree2, fname);
1264 talloc_free(tree);
1267 talloc_free(mem_ctx);
1269 return ret;
1273 * basic test for doing a durable open:
1274 * logoff, create a new session, do a durable reopen (succeeds)
1276 static bool test_durable_open_reopen4(struct torture_context *tctx,
1277 struct smb2_tree *tree)
1279 NTSTATUS status;
1280 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1281 char fname[256];
1282 struct smb2_handle _h;
1283 struct smb2_handle *h = NULL;
1284 struct smb2_create io1, io2;
1285 bool ret = true;
1286 struct smb2_transport *transport;
1287 struct smb2_session *session2;
1288 struct smb2_tree *tree2;
1290 /* Choose a random name in case the state is left a little funky. */
1291 snprintf(fname, 256, "durable_open_reopen4_%s.dat",
1292 generate_random_str(tctx, 8));
1294 smb2_util_unlink(tree, fname);
1296 smb2_oplock_create_share(&io1, fname,
1297 smb2_util_share_access(""),
1298 smb2_util_oplock_level("b"));
1299 io1.in.durable_open = true;
1300 io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1302 status = smb2_create(tree, mem_ctx, &io1);
1303 CHECK_STATUS(status, NT_STATUS_OK);
1304 _h = io1.out.file.handle;
1305 h = &_h;
1306 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1307 CHECK_VAL(io1.out.durable_open, true);
1308 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1311 * do a session logoff, establish a new session and tree
1312 * connect on the same transport, and try a durable reopen
1314 transport = tree->session->transport;
1315 status = smb2_logoff(tree->session);
1316 CHECK_STATUS(status, NT_STATUS_OK);
1318 if (!torture_smb2_session_setup(tctx, transport,
1319 0, /* previous_session_id */
1320 mem_ctx, &session2))
1322 torture_warning(tctx, "session setup failed.\n");
1323 ret = false;
1324 goto done;
1328 * the session setup has talloc-stolen the transport,
1329 * so we can safely free the old tree+session for clarity
1331 TALLOC_FREE(tree);
1333 if (!torture_smb2_tree_connect(tctx, session2, mem_ctx, &tree2)) {
1334 torture_warning(tctx, "tree connect failed.\n");
1335 ret = false;
1336 goto done;
1339 ZERO_STRUCT(io2);
1340 io2.in.fname = fname;
1341 io2.in.durable_handle = h;
1342 h = NULL;
1344 status = smb2_create(tree2, mem_ctx, &io2);
1345 CHECK_STATUS(status, NT_STATUS_OK);
1347 _h = io2.out.file.handle;
1348 h = &_h;
1349 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1350 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
1352 done:
1353 if (tree != NULL) {
1354 if (h != NULL) {
1355 smb2_util_close(tree2, *h);
1358 smb2_util_unlink(tree2, fname);
1360 talloc_free(tree);
1363 talloc_free(mem_ctx);
1365 return ret;
1368 static bool test_durable_open_delete_on_close1(struct torture_context *tctx,
1369 struct smb2_tree *tree)
1371 NTSTATUS status;
1372 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1373 char fname[256];
1374 struct smb2_handle _h;
1375 struct smb2_handle *h = NULL;
1376 struct smb2_create io1, io2;
1377 bool ret = true;
1378 uint8_t b = 0;
1380 /* Choose a random name in case the state is left a little funky. */
1381 snprintf(fname, 256, "durable_open_delete_on_close1_%s.dat",
1382 generate_random_str(tctx, 8));
1384 smb2_util_unlink(tree, fname);
1386 smb2_oplock_create_share(&io1, fname,
1387 smb2_util_share_access(""),
1388 smb2_util_oplock_level("b"));
1389 io1.in.durable_open = true;
1390 io1.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1392 status = smb2_create(tree, mem_ctx, &io1);
1393 CHECK_STATUS(status, NT_STATUS_OK);
1394 _h = io1.out.file.handle;
1395 h = &_h;
1396 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1397 CHECK_VAL(io1.out.durable_open, true);
1398 CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
1400 status = smb2_util_write(tree, *h, &b, 0, 1);
1401 CHECK_STATUS(status, NT_STATUS_OK);
1403 /* disconnect, leaving the durable handle in place */
1404 TALLOC_FREE(tree);
1406 if (!torture_smb2_connection(tctx, &tree)) {
1407 torture_warning(tctx, "could not reconnect, bailing\n");
1408 ret = false;
1409 goto done;
1413 * Open the file on the new connection again
1414 * and check that it has been newly created,
1415 * i.e. delete on close was effective on the disconnected handle.
1416 * Also check that the file is really empty,
1417 * the previously written byte gone.
1419 smb2_oplock_create_share(&io2, fname,
1420 smb2_util_share_access(""),
1421 smb2_util_oplock_level("b"));
1422 io2.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1424 status = smb2_create(tree, mem_ctx, &io2);
1425 CHECK_STATUS(status, NT_STATUS_OK);
1426 _h = io2.out.file.handle;
1427 h = &_h;
1428 CHECK_CREATED_SIZE(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE, 0, 0);
1429 CHECK_VAL(io2.out.durable_open, false);
1430 CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
1432 done:
1433 if (tree != NULL) {
1434 if (h != NULL) {
1435 smb2_util_close(tree, *h);
1438 smb2_util_unlink(tree, fname);
1440 talloc_free(tree);
1443 talloc_free(mem_ctx);
1445 return ret;
1449 static bool test_durable_open_delete_on_close2(struct torture_context *tctx,
1450 struct smb2_tree *tree)
1452 NTSTATUS status;
1453 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1454 char fname[256];
1455 struct smb2_handle _h;
1456 struct smb2_handle *h = NULL;
1457 struct smb2_create io;
1458 bool ret = true;
1459 uint8_t b = 0;
1460 uint64_t previous_session_id;
1461 uint64_t alloc_size_step;
1462 struct smbcli_options options;
1464 options = tree->session->transport->options;
1466 /* Choose a random name in case the state is left a little funky. */
1467 snprintf(fname, 256, "durable_open_delete_on_close2_%s.dat",
1468 generate_random_str(tctx, 8));
1470 smb2_util_unlink(tree, fname);
1472 smb2_oplock_create_share(&io, fname,
1473 smb2_util_share_access(""),
1474 smb2_util_oplock_level("b"));
1475 io.in.durable_open = true;
1476 io.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1478 status = smb2_create(tree, mem_ctx, &io);
1479 CHECK_STATUS(status, NT_STATUS_OK);
1480 _h = io.out.file.handle;
1481 h = &_h;
1482 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1483 CHECK_VAL(io.out.durable_open, true);
1484 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
1486 status = smb2_util_write(tree, *h, &b, 0, 1);
1487 CHECK_STATUS(status, NT_STATUS_OK);
1489 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
1491 /* disconnect, leaving the durable handle in place */
1492 TALLOC_FREE(tree);
1494 if (!torture_smb2_connection_ext(tctx, previous_session_id,
1495 &options, &tree))
1497 torture_warning(tctx, "could not reconnect, bailing\n");
1498 ret = false;
1499 goto done;
1502 ZERO_STRUCT(io);
1503 io.in.fname = fname;
1504 io.in.durable_handle = h;
1506 status = smb2_create(tree, mem_ctx, &io);
1507 CHECK_STATUS(status, NT_STATUS_OK);
1508 _h = io.out.file.handle;
1509 h = &_h;
1510 alloc_size_step = io.out.alloc_size;
1511 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE, alloc_size_step, 1);
1512 CHECK_VAL(io.out.durable_open, false);
1513 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
1515 /* close the file, thereby deleting it */
1516 smb2_util_close(tree, *h);
1517 status = smb2_logoff(tree->session);
1518 TALLOC_FREE(tree);
1520 if (!torture_smb2_connection(tctx, &tree)) {
1521 torture_warning(tctx, "could not reconnect, bailing\n");
1522 ret = false;
1523 goto done;
1527 * Open the file on the new connection again
1528 * and check that it has been newly created,
1529 * i.e. delete on close was effective on the reconnected handle.
1530 * Also check that the file is really empty,
1531 * the previously written byte gone.
1533 smb2_oplock_create_share(&io, fname,
1534 smb2_util_share_access(""),
1535 smb2_util_oplock_level("b"));
1536 io.in.durable_open = true;
1537 io.in.create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
1539 status = smb2_create(tree, mem_ctx, &io);
1540 CHECK_STATUS(status, NT_STATUS_OK);
1541 _h = io.out.file.handle;
1542 h = &_h;
1543 CHECK_CREATED_SIZE(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE, 0, 0);
1544 CHECK_VAL(io.out.durable_open, true);
1545 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
1547 done:
1548 if (tree != NULL) {
1549 if (h != NULL) {
1550 smb2_util_close(tree, *h);
1553 smb2_util_unlink(tree, fname);
1555 talloc_free(tree);
1558 talloc_free(mem_ctx);
1560 return ret;
1564 basic testing of SMB2 durable opens
1565 regarding the position information on the handle
1567 static bool test_durable_open_file_position(struct torture_context *tctx,
1568 struct smb2_tree *tree)
1570 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1571 struct smb2_handle h;
1572 struct smb2_create io;
1573 NTSTATUS status;
1574 const char *fname = "durable_open_position.dat";
1575 union smb_fileinfo qfinfo;
1576 union smb_setfileinfo sfinfo;
1577 bool ret = true;
1578 uint64_t pos;
1579 uint64_t previous_session_id;
1580 struct smbcli_options options;
1582 options = tree->session->transport->options;
1584 smb2_util_unlink(tree, fname);
1586 smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_BATCH);
1587 io.in.durable_open = true;
1589 status = smb2_create(tree, mem_ctx, &io);
1590 CHECK_STATUS(status, NT_STATUS_OK);
1591 h = io.out.file.handle;
1592 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1593 CHECK_VAL(io.out.durable_open, true);
1594 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1596 /* TODO: check extra blob content */
1598 ZERO_STRUCT(qfinfo);
1599 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1600 qfinfo.generic.in.file.handle = h;
1601 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1602 CHECK_STATUS(status, NT_STATUS_OK);
1603 CHECK_VAL(qfinfo.position_information.out.position, 0);
1604 pos = qfinfo.position_information.out.position;
1605 torture_comment(tctx, "position: %llu\n",
1606 (unsigned long long)pos);
1608 ZERO_STRUCT(sfinfo);
1609 sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION;
1610 sfinfo.generic.in.file.handle = h;
1611 sfinfo.position_information.in.position = 0x1000;
1612 status = smb2_setinfo_file(tree, &sfinfo);
1613 CHECK_STATUS(status, NT_STATUS_OK);
1615 ZERO_STRUCT(qfinfo);
1616 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1617 qfinfo.generic.in.file.handle = h;
1618 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1619 CHECK_STATUS(status, NT_STATUS_OK);
1620 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
1621 pos = qfinfo.position_information.out.position;
1622 torture_comment(tctx, "position: %llu\n",
1623 (unsigned long long)pos);
1625 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
1627 /* tcp disconnect */
1628 talloc_free(tree);
1629 tree = NULL;
1631 /* do a session reconnect */
1632 if (!torture_smb2_connection_ext(tctx, previous_session_id,
1633 &options, &tree))
1635 torture_warning(tctx, "couldn't reconnect, bailing\n");
1636 ret = false;
1637 goto done;
1640 ZERO_STRUCT(qfinfo);
1641 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1642 qfinfo.generic.in.file.handle = h;
1643 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1644 CHECK_STATUS(status, NT_STATUS_FILE_CLOSED);
1646 ZERO_STRUCT(io);
1647 io.in.fname = fname;
1648 io.in.durable_handle = &h;
1650 status = smb2_create(tree, mem_ctx, &io);
1651 CHECK_STATUS(status, NT_STATUS_OK);
1652 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1653 CHECK_VAL(io.out.reserved, 0x00);
1654 CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED);
1655 CHECK_VAL(io.out.alloc_size, 0);
1656 CHECK_VAL(io.out.size, 0);
1657 CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE);
1658 CHECK_VAL(io.out.reserved2, 0);
1660 h = io.out.file.handle;
1662 ZERO_STRUCT(qfinfo);
1663 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
1664 qfinfo.generic.in.file.handle = h;
1665 status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
1666 CHECK_STATUS(status, NT_STATUS_OK);
1667 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
1668 pos = qfinfo.position_information.out.position;
1669 torture_comment(tctx, "position: %llu\n",
1670 (unsigned long long)pos);
1672 smb2_util_close(tree, h);
1674 talloc_free(mem_ctx);
1676 smb2_util_unlink(tree, fname);
1678 done:
1679 talloc_free(tree);
1681 return ret;
1685 Open, disconnect, oplock break, reconnect.
1687 static bool test_durable_open_oplock(struct torture_context *tctx,
1688 struct smb2_tree *tree1,
1689 struct smb2_tree *tree2)
1691 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1692 struct smb2_create io1, io2;
1693 struct smb2_handle h1, h2;
1694 NTSTATUS status;
1695 char fname[256];
1696 bool ret = true;
1698 /* Choose a random name in case the state is left a little funky. */
1699 snprintf(fname, 256, "durable_open_oplock_%s.dat", generate_random_str(tctx, 8));
1701 /* Clean slate */
1702 smb2_util_unlink(tree1, fname);
1704 /* Create with batch oplock */
1705 smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH);
1706 io1.in.durable_open = true;
1708 io2 = io1;
1709 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
1711 status = smb2_create(tree1, mem_ctx, &io1);
1712 CHECK_STATUS(status, NT_STATUS_OK);
1713 h1 = io1.out.file.handle;
1714 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1715 CHECK_VAL(io1.out.durable_open, true);
1716 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1718 /* Disconnect after getting the batch */
1719 talloc_free(tree1);
1720 tree1 = NULL;
1723 * Windows7 (build 7000) will break a batch oplock immediately if the
1724 * original client is gone. (ZML: This seems like a bug. It should give
1725 * some time for the client to reconnect!)
1727 status = smb2_create(tree2, mem_ctx, &io2);
1728 CHECK_STATUS(status, NT_STATUS_OK);
1729 h2 = io2.out.file.handle;
1730 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1731 CHECK_VAL(io2.out.durable_open, true);
1732 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
1734 /* What if tree1 tries to come back and reclaim? */
1735 if (!torture_smb2_connection(tctx, &tree1)) {
1736 torture_warning(tctx, "couldn't reconnect, bailing\n");
1737 ret = false;
1738 goto done;
1741 ZERO_STRUCT(io1);
1742 io1.in.fname = fname;
1743 io1.in.durable_handle = &h1;
1745 status = smb2_create(tree1, mem_ctx, &io1);
1746 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1748 done:
1749 smb2_util_close(tree2, h2);
1750 smb2_util_unlink(tree2, fname);
1752 talloc_free(tree1);
1753 talloc_free(tree2);
1755 return ret;
1759 Open, disconnect, lease break, reconnect.
1761 static bool test_durable_open_lease(struct torture_context *tctx,
1762 struct smb2_tree *tree1,
1763 struct smb2_tree *tree2)
1765 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1766 struct smb2_create io1, io2;
1767 struct smb2_lease ls1, ls2;
1768 struct smb2_handle h1, h2;
1769 NTSTATUS status;
1770 char fname[256];
1771 bool ret = true;
1772 uint64_t lease1, lease2;
1773 uint32_t caps;
1775 caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
1776 if (!(caps & SMB2_CAP_LEASING)) {
1777 torture_skip(tctx, "leases are not supported");
1781 * Choose a random name and random lease in case the state is left a
1782 * little funky.
1784 lease1 = random();
1785 lease2 = random();
1786 snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8));
1788 /* Clean slate */
1789 smb2_util_unlink(tree1, fname);
1791 /* Create with lease */
1792 smb2_lease_create(&io1, &ls1, false /* dir */, fname,
1793 lease1, smb2_util_lease_state("RHW"));
1794 io1.in.durable_open = true;
1796 smb2_lease_create(&io2, &ls2, false /* dir */, fname,
1797 lease2, smb2_util_lease_state("RHW"));
1798 io2.in.durable_open = true;
1799 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
1801 status = smb2_create(tree1, mem_ctx, &io1);
1802 CHECK_STATUS(status, NT_STATUS_OK);
1803 h1 = io1.out.file.handle;
1804 CHECK_VAL(io1.out.durable_open, true);
1805 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1807 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1808 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease1);
1809 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease1);
1810 CHECK_VAL(io1.out.lease_response.lease_state,
1811 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
1813 /* Disconnect after getting the lease */
1814 talloc_free(tree1);
1815 tree1 = NULL;
1818 * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?)
1819 * even if the original client is gone. (ZML: This seems like a bug. It
1820 * should give some time for the client to reconnect! And why RH?)
1822 * obnox: Current windows 7 and w2k8r2 grant RHW instead of RH.
1823 * Test is adapted accordingly.
1825 status = smb2_create(tree2, mem_ctx, &io2);
1826 CHECK_STATUS(status, NT_STATUS_OK);
1827 h2 = io2.out.file.handle;
1828 CHECK_VAL(io2.out.durable_open, true);
1829 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
1831 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1832 CHECK_VAL(io2.out.lease_response.lease_key.data[0], lease2);
1833 CHECK_VAL(io2.out.lease_response.lease_key.data[1], ~lease2);
1834 CHECK_VAL(io2.out.lease_response.lease_state,
1835 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
1837 /* What if tree1 tries to come back and reclaim? */
1838 if (!torture_smb2_connection(tctx, &tree1)) {
1839 torture_warning(tctx, "couldn't reconnect, bailing\n");
1840 ret = false;
1841 goto done;
1844 ZERO_STRUCT(io1);
1845 io1.in.fname = fname;
1846 io1.in.durable_handle = &h1;
1847 io1.in.lease_request = &ls1;
1849 status = smb2_create(tree1, mem_ctx, &io1);
1850 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
1852 done:
1853 smb2_util_close(tree2, h2);
1854 smb2_util_unlink(tree2, fname);
1856 talloc_free(tree1);
1857 talloc_free(tree2);
1859 return ret;
1862 static bool test_durable_open_lock_oplock(struct torture_context *tctx,
1863 struct smb2_tree *tree)
1865 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1866 struct smb2_create io;
1867 struct smb2_handle h;
1868 struct smb2_lock lck;
1869 struct smb2_lock_element el[2];
1870 NTSTATUS status;
1871 char fname[256];
1872 bool ret = true;
1876 snprintf(fname, 256, "durable_open_oplock_lock_%s.dat", generate_random_str(tctx, 8));
1878 /* Clean slate */
1879 smb2_util_unlink(tree, fname);
1881 /* Create with oplock */
1883 smb2_oplock_create_share(&io, fname,
1884 smb2_util_share_access(""),
1885 smb2_util_oplock_level("b"));
1886 io.in.durable_open = true;
1888 status = smb2_create(tree, mem_ctx, &io);
1889 CHECK_STATUS(status, NT_STATUS_OK);
1890 h = io.out.file.handle;
1891 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1893 CHECK_VAL(io.out.durable_open, true);
1894 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
1896 ZERO_STRUCT(lck);
1897 ZERO_STRUCT(el);
1898 lck.in.locks = el;
1899 lck.in.lock_count = 0x0001;
1900 lck.in.lock_sequence = 0x00000000;
1901 lck.in.file.handle = h;
1902 el[0].offset = 0;
1903 el[0].length = 1;
1904 el[0].reserved = 0x00000000;
1905 el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
1906 status = smb2_lock(tree, &lck);
1907 CHECK_STATUS(status, NT_STATUS_OK);
1909 /* Disconnect/Reconnect. */
1910 talloc_free(tree);
1911 tree = NULL;
1913 if (!torture_smb2_connection(tctx, &tree)) {
1914 torture_warning(tctx, "couldn't reconnect, bailing\n");
1915 ret = false;
1916 goto done;
1919 ZERO_STRUCT(io);
1920 io.in.fname = fname;
1921 io.in.durable_handle = &h;
1923 status = smb2_create(tree, mem_ctx, &io);
1924 CHECK_STATUS(status, NT_STATUS_OK);
1925 h = io.out.file.handle;
1927 lck.in.file.handle = h;
1928 el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
1929 status = smb2_lock(tree, &lck);
1930 CHECK_STATUS(status, NT_STATUS_OK);
1932 done:
1933 smb2_util_close(tree, h);
1934 smb2_util_unlink(tree, fname);
1935 talloc_free(tree);
1937 return ret;
1941 Open, take BRL, disconnect, reconnect.
1943 static bool test_durable_open_lock_lease(struct torture_context *tctx,
1944 struct smb2_tree *tree)
1946 TALLOC_CTX *mem_ctx = talloc_new(tctx);
1947 struct smb2_create io;
1948 struct smb2_lease ls;
1949 struct smb2_handle h;
1950 struct smb2_lock lck;
1951 struct smb2_lock_element el[2];
1952 NTSTATUS status;
1953 char fname[256];
1954 bool ret = true;
1955 uint64_t lease;
1956 uint32_t caps;
1957 struct smbcli_options options;
1959 options = tree->session->transport->options;
1961 caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
1962 if (!(caps & SMB2_CAP_LEASING)) {
1963 torture_skip(tctx, "leases are not supported");
1967 * Choose a random name and random lease in case the state is left a
1968 * little funky.
1970 lease = random();
1971 snprintf(fname, 256, "durable_open_lease_lock_%s.dat", generate_random_str(tctx, 8));
1973 /* Clean slate */
1974 smb2_util_unlink(tree, fname);
1976 /* Create with lease */
1978 smb2_lease_create(&io, &ls, false /* dir */, fname, lease,
1979 smb2_util_lease_state("RWH"));
1980 io.in.durable_open = true;
1982 status = smb2_create(tree, mem_ctx, &io);
1983 CHECK_STATUS(status, NT_STATUS_OK);
1984 h = io.out.file.handle;
1985 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
1987 CHECK_VAL(io.out.durable_open, true);
1988 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
1989 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
1990 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
1991 CHECK_VAL(io.out.lease_response.lease_state,
1992 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
1994 ZERO_STRUCT(lck);
1995 ZERO_STRUCT(el);
1996 lck.in.locks = el;
1997 lck.in.lock_count = 0x0001;
1998 lck.in.lock_sequence = 0x00000000;
1999 lck.in.file.handle = h;
2000 el[0].offset = 0;
2001 el[0].length = 1;
2002 el[0].reserved = 0x00000000;
2003 el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
2004 status = smb2_lock(tree, &lck);
2005 CHECK_STATUS(status, NT_STATUS_OK);
2007 /* Disconnect/Reconnect. */
2008 talloc_free(tree);
2009 tree = NULL;
2011 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) {
2012 torture_warning(tctx, "couldn't reconnect, bailing\n");
2013 ret = false;
2014 goto done;
2017 ZERO_STRUCT(io);
2018 io.in.fname = fname;
2019 io.in.durable_handle = &h;
2020 io.in.lease_request = &ls;
2022 status = smb2_create(tree, mem_ctx, &io);
2023 CHECK_STATUS(status, NT_STATUS_OK);
2024 h = io.out.file.handle;
2026 lck.in.file.handle = h;
2027 el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
2028 status = smb2_lock(tree, &lck);
2029 CHECK_STATUS(status, NT_STATUS_OK);
2031 done:
2032 smb2_util_close(tree, h);
2033 smb2_util_unlink(tree, fname);
2034 talloc_free(tree);
2036 return ret;
2040 * Open with a RH lease, disconnect, open in another tree, reconnect.
2042 * This test actually demonstrates a minimum level of respect for the durable
2043 * open in the face of another open. As long as this test shows an inability to
2044 * reconnect after an open, the oplock/lease tests above will certainly
2045 * demonstrate an error on reconnect.
2047 static bool test_durable_open_open2_lease(struct torture_context *tctx,
2048 struct smb2_tree *tree1,
2049 struct smb2_tree *tree2)
2051 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2052 struct smb2_create io1, io2;
2053 struct smb2_lease ls;
2054 struct smb2_handle h1, h2;
2055 NTSTATUS status;
2056 char fname[256];
2057 bool ret = true;
2058 uint64_t lease;
2059 uint32_t caps;
2060 struct smbcli_options options;
2062 options = tree1->session->transport->options;
2064 caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
2065 if (!(caps & SMB2_CAP_LEASING)) {
2066 torture_skip(tctx, "leases are not supported");
2070 * Choose a random name and random lease in case the state is left a
2071 * little funky.
2073 lease = random();
2074 snprintf(fname, 256, "durable_open_open2_lease_%s.dat",
2075 generate_random_str(tctx, 8));
2077 /* Clean slate */
2078 smb2_util_unlink(tree1, fname);
2080 /* Create with lease */
2081 smb2_lease_create_share(&io1, &ls, false /* dir */, fname,
2082 smb2_util_share_access(""),
2083 lease,
2084 smb2_util_lease_state("RH"));
2085 io1.in.durable_open = true;
2087 status = smb2_create(tree1, mem_ctx, &io1);
2088 CHECK_STATUS(status, NT_STATUS_OK);
2089 h1 = io1.out.file.handle;
2090 CHECK_VAL(io1.out.durable_open, true);
2091 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2093 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
2094 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease);
2095 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease);
2096 CHECK_VAL(io1.out.lease_response.lease_state,
2097 smb2_util_lease_state("RH"));
2099 /* Disconnect */
2100 talloc_free(tree1);
2101 tree1 = NULL;
2103 /* Open the file in tree2 */
2104 smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE);
2106 status = smb2_create(tree2, mem_ctx, &io2);
2107 CHECK_STATUS(status, NT_STATUS_OK);
2108 h2 = io2.out.file.handle;
2109 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2111 /* Reconnect */
2112 if (!torture_smb2_connection_ext(tctx, 0, &options, &tree1)) {
2113 torture_warning(tctx, "couldn't reconnect, bailing\n");
2114 ret = false;
2115 goto done;
2118 ZERO_STRUCT(io1);
2119 io1.in.fname = fname;
2120 io1.in.durable_handle = &h1;
2121 io1.in.lease_request = &ls;
2124 * Windows7 (build 7000) will give away an open immediately if the
2125 * original client is gone. (ZML: This seems like a bug. It should give
2126 * some time for the client to reconnect!)
2128 status = smb2_create(tree1, mem_ctx, &io1);
2129 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
2130 h1 = io1.out.file.handle;
2132 done:
2133 smb2_util_close(tree2, h2);
2134 smb2_util_unlink(tree2, fname);
2135 smb2_util_close(tree1, h1);
2136 smb2_util_unlink(tree1, fname);
2138 talloc_free(tree1);
2139 talloc_free(tree2);
2141 return ret;
2145 * Open with a batch oplock, disconnect, open in another tree, reconnect.
2147 * This test actually demonstrates a minimum level of respect for the durable
2148 * open in the face of another open. As long as this test shows an inability to
2149 * reconnect after an open, the oplock/lease tests above will certainly
2150 * demonstrate an error on reconnect.
2152 static bool test_durable_open_open2_oplock(struct torture_context *tctx,
2153 struct smb2_tree *tree1,
2154 struct smb2_tree *tree2)
2156 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2157 struct smb2_create io1, io2;
2158 struct smb2_handle h1, h2;
2159 NTSTATUS status;
2160 char fname[256];
2161 bool ret = true;
2164 * Choose a random name and random lease in case the state is left a
2165 * little funky.
2167 snprintf(fname, 256, "durable_open_open2_oplock_%s.dat",
2168 generate_random_str(tctx, 8));
2170 /* Clean slate */
2171 smb2_util_unlink(tree1, fname);
2173 /* Create with batch oplock */
2174 smb2_oplock_create(&io1, fname, SMB2_OPLOCK_LEVEL_BATCH);
2175 io1.in.durable_open = true;
2177 status = smb2_create(tree1, mem_ctx, &io1);
2178 CHECK_STATUS(status, NT_STATUS_OK);
2179 h1 = io1.out.file.handle;
2180 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2181 CHECK_VAL(io1.out.durable_open, true);
2182 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
2184 /* Disconnect */
2185 talloc_free(tree1);
2186 tree1 = NULL;
2188 /* Open the file in tree2 */
2189 smb2_oplock_create(&io2, fname, SMB2_OPLOCK_LEVEL_NONE);
2191 status = smb2_create(tree2, mem_ctx, &io2);
2192 CHECK_STATUS(status, NT_STATUS_OK);
2193 h2 = io2.out.file.handle;
2194 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2196 /* Reconnect */
2197 if (!torture_smb2_connection(tctx, &tree1)) {
2198 torture_warning(tctx, "couldn't reconnect, bailing\n");
2199 ret = false;
2200 goto done;
2203 ZERO_STRUCT(io1);
2204 io1.in.fname = fname;
2205 io1.in.durable_handle = &h1;
2207 status = smb2_create(tree1, mem_ctx, &io1);
2208 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
2209 h1 = io1.out.file.handle;
2211 done:
2212 smb2_util_close(tree2, h2);
2213 smb2_util_unlink(tree2, fname);
2214 if (tree1 != NULL) {
2215 smb2_util_close(tree1, h1);
2216 smb2_util_unlink(tree1, fname);
2219 talloc_free(tree1);
2220 talloc_free(tree2);
2222 return ret;
2226 * test behaviour with initial allocation size
2228 static bool test_durable_open_alloc_size(struct torture_context *tctx,
2229 struct smb2_tree *tree)
2231 NTSTATUS status;
2232 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2233 char fname[256];
2234 struct smb2_handle _h;
2235 struct smb2_handle *h = NULL;
2236 struct smb2_create io;
2237 bool ret = true;
2238 uint64_t previous_session_id;
2239 uint64_t alloc_size_step;
2240 uint64_t initial_alloc_size = 0x100;
2241 const uint8_t *b = NULL;
2242 struct smbcli_options options;
2244 options = tree->session->transport->options;
2246 /* Choose a random name in case the state is left a little funky. */
2247 snprintf(fname, 256, "durable_open_alloc_size_%s.dat",
2248 generate_random_str(tctx, 8));
2250 smb2_util_unlink(tree, fname);
2252 smb2_oplock_create_share(&io, fname,
2253 smb2_util_share_access(""),
2254 smb2_util_oplock_level("b"));
2255 io.in.durable_open = true;
2256 io.in.alloc_size = initial_alloc_size;
2258 status = smb2_create(tree, mem_ctx, &io);
2259 CHECK_STATUS(status, NT_STATUS_OK);
2260 _h = io.out.file.handle;
2261 h = &_h;
2262 CHECK_NOT_VAL(io.out.alloc_size, 0);
2263 alloc_size_step = io.out.alloc_size;
2264 CHECK_CREATED_SIZE(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE,
2265 alloc_size_step, 0);
2266 CHECK_VAL(io.out.durable_open, true);
2267 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2269 /* prepare buffer */
2270 b = talloc_zero_size(mem_ctx, alloc_size_step);
2271 CHECK_NOT_VAL(b, NULL);
2273 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2275 /* disconnect, reconnect and then do durable reopen */
2276 talloc_free(tree);
2277 tree = NULL;
2279 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2280 &options, &tree))
2282 torture_warning(tctx, "couldn't reconnect, bailing\n");
2283 ret = false;
2284 goto done;
2287 ZERO_STRUCT(io);
2288 io.in.fname = fname;
2289 io.in.durable_handle = h;
2290 h = NULL;
2292 status = smb2_create(tree, mem_ctx, &io);
2293 CHECK_STATUS(status, NT_STATUS_OK);
2294 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE,
2295 alloc_size_step, 0);
2296 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2297 _h = io.out.file.handle;
2298 h = &_h;
2300 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2302 /* write one byte */
2303 status = smb2_util_write(tree, *h, b, 0, 1);
2304 CHECK_STATUS(status, NT_STATUS_OK);
2306 /* disconnect, reconnect and then do durable reopen */
2307 talloc_free(tree);
2308 tree = NULL;
2310 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2311 &options, &tree))
2313 torture_warning(tctx, "couldn't reconnect, bailing\n");
2314 ret = false;
2315 goto done;
2318 ZERO_STRUCT(io);
2319 io.in.fname = fname;
2320 io.in.durable_handle = h;
2321 h = NULL;
2323 status = smb2_create(tree, mem_ctx, &io);
2324 CHECK_STATUS(status, NT_STATUS_OK);
2325 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE,
2326 alloc_size_step, 1);
2327 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2328 _h = io.out.file.handle;
2329 h = &_h;
2331 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2333 /* write more byte than initial allocation size */
2334 status = smb2_util_write(tree, *h, b, 1, alloc_size_step);
2336 /* disconnect, reconnect and then do durable reopen */
2337 talloc_free(tree);
2338 tree = NULL;
2340 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2341 &options, &tree))
2343 torture_warning(tctx, "couldn't reconnect, bailing\n");
2344 ret = false;
2345 goto done;
2348 ZERO_STRUCT(io);
2349 io.in.fname = fname;
2350 io.in.durable_handle = h;
2351 h = NULL;
2353 status = smb2_create(tree, mem_ctx, &io);
2354 CHECK_STATUS(status, NT_STATUS_OK);
2355 CHECK_CREATED_SIZE(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE,
2356 alloc_size_step * 2, alloc_size_step + 1);
2357 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2358 _h = io.out.file.handle;
2359 h = &_h;
2361 done:
2362 if (h != NULL) {
2363 smb2_util_close(tree, *h);
2366 smb2_util_unlink(tree, fname);
2368 talloc_free(tree);
2370 talloc_free(mem_ctx);
2372 return ret;
2376 * test behaviour when a disconnect happens while creating a read-only file
2378 static bool test_durable_open_read_only(struct torture_context *tctx,
2379 struct smb2_tree *tree)
2381 NTSTATUS status;
2382 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2383 char fname[256];
2384 struct smb2_handle _h;
2385 struct smb2_handle *h = NULL;
2386 struct smb2_create io;
2387 bool ret = true;
2388 uint64_t previous_session_id;
2389 const uint8_t b = 0;
2390 uint64_t alloc_size = 0;
2391 struct smbcli_options options;
2393 options = tree->session->transport->options;
2395 /* Choose a random name in case the state is left a little funky. */
2396 snprintf(fname, 256, "durable_open_initial_alloc_%s.dat",
2397 generate_random_str(tctx, 8));
2399 smb2_util_unlink(tree, fname);
2401 smb2_oplock_create_share(&io, fname,
2402 smb2_util_share_access(""),
2403 smb2_util_oplock_level("b"));
2404 io.in.durable_open = true;
2405 io.in.file_attributes = FILE_ATTRIBUTE_READONLY;
2407 status = smb2_create(tree, mem_ctx, &io);
2408 CHECK_STATUS(status, NT_STATUS_OK);
2409 _h = io.out.file.handle;
2410 h = &_h;
2411 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE);
2412 CHECK_VAL(io.out.durable_open, true);
2413 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2415 previous_session_id = smb2cli_session_current_id(tree->session->smbXcli);
2417 /* write one byte */
2418 status = smb2_util_write(tree, *h, &b, 0, 1);
2419 CHECK_STATUS(status, NT_STATUS_OK);
2421 /* disconnect, reconnect and then do durable reopen */
2422 talloc_free(tree);
2423 tree = NULL;
2425 if (!torture_smb2_connection_ext(tctx, previous_session_id,
2426 &options, &tree))
2428 torture_warning(tctx, "couldn't reconnect, bailing\n");
2429 ret = false;
2430 goto done;
2433 ZERO_STRUCT(io);
2434 io.in.fname = fname;
2435 io.in.durable_handle = h;
2436 h = NULL;
2438 status = smb2_create(tree, mem_ctx, &io);
2439 CHECK_STATUS(status, NT_STATUS_OK);
2440 alloc_size = io.out.alloc_size;
2441 CHECK_CREATED_SIZE(&io, EXISTED,
2442 FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE,
2443 alloc_size, 1);
2444 CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
2445 _h = io.out.file.handle;
2446 h = &_h;
2448 /* write one byte */
2449 status = smb2_util_write(tree, *h, &b, 1, 1);
2450 CHECK_STATUS(status, NT_STATUS_OK);
2452 done:
2453 if (h != NULL) {
2454 union smb_setfileinfo sfinfo;
2456 ZERO_STRUCT(sfinfo);
2457 sfinfo.basic_info.level = RAW_SFILEINFO_BASIC_INFORMATION;
2458 sfinfo.basic_info.in.file.handle = *h;
2459 sfinfo.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL;
2460 smb2_setinfo_file(tree, &sfinfo);
2462 smb2_util_close(tree, *h);
2465 smb2_util_unlink(tree, fname);
2467 talloc_free(tree);
2469 talloc_free(mem_ctx);
2471 return ret;
2475 * durable open with oplock, disconnect, exit
2477 static bool test_durable_open_oplock_disconnect(struct torture_context *tctx,
2478 struct smb2_tree *tree)
2480 TALLOC_CTX *mem_ctx = talloc_new(tctx);
2481 struct smb2_create io;
2482 struct smb2_handle _h;
2483 struct smb2_handle *h = NULL;
2484 NTSTATUS status;
2485 char fname[256];
2486 bool ret = true;
2488 snprintf(fname, 256, "durable_open_oplock_disconnect_%s.dat",
2489 generate_random_str(mem_ctx, 8));
2491 smb2_util_unlink(tree, fname);
2493 smb2_oplock_create(&io, fname, SMB2_OPLOCK_LEVEL_BATCH);
2494 io.in.durable_open = true;
2496 status = smb2_create(tree, mem_ctx, &io);
2497 CHECK_STATUS(status, NT_STATUS_OK);
2499 _h = io.out.file.handle;
2500 h = &_h;
2502 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
2503 CHECK_VAL(io.out.durable_open, true);
2504 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
2506 /* disconnect */
2507 talloc_free(tree);
2508 tree = NULL;
2510 done:
2511 if (tree != NULL) {
2512 if (h != NULL) {
2513 smb2_util_close(tree, *h);
2515 smb2_util_unlink(tree, fname);
2517 talloc_free(mem_ctx);
2518 return ret;
2522 struct torture_suite *torture_smb2_durable_open_init(void)
2524 struct torture_suite *suite =
2525 torture_suite_create(talloc_autofree_context(), "durable-open");
2527 torture_suite_add_1smb2_test(suite, "open-oplock", test_durable_open_open_oplock);
2528 torture_suite_add_1smb2_test(suite, "open-lease", test_durable_open_open_lease);
2529 torture_suite_add_1smb2_test(suite, "reopen1", test_durable_open_reopen1);
2530 torture_suite_add_1smb2_test(suite, "reopen1a", test_durable_open_reopen1a);
2531 torture_suite_add_1smb2_test(suite, "reopen2", test_durable_open_reopen2);
2532 torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_open_reopen2_lease);
2533 torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_open_reopen2_lease_v2);
2534 torture_suite_add_1smb2_test(suite, "reopen2a", test_durable_open_reopen2a);
2535 torture_suite_add_1smb2_test(suite, "reopen3", test_durable_open_reopen3);
2536 torture_suite_add_1smb2_test(suite, "reopen4", test_durable_open_reopen4);
2537 torture_suite_add_1smb2_test(suite, "delete_on_close1",
2538 test_durable_open_delete_on_close1);
2539 torture_suite_add_1smb2_test(suite, "delete_on_close2",
2540 test_durable_open_delete_on_close2);
2541 torture_suite_add_1smb2_test(suite, "file-position",
2542 test_durable_open_file_position);
2543 torture_suite_add_2smb2_test(suite, "oplock", test_durable_open_oplock);
2544 torture_suite_add_2smb2_test(suite, "lease", test_durable_open_lease);
2545 torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_open_lock_oplock);
2546 torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_open_lock_lease);
2547 torture_suite_add_2smb2_test(suite, "open2-lease",
2548 test_durable_open_open2_lease);
2549 torture_suite_add_2smb2_test(suite, "open2-oplock",
2550 test_durable_open_open2_oplock);
2551 torture_suite_add_1smb2_test(suite, "alloc-size",
2552 test_durable_open_alloc_size);
2553 torture_suite_add_1smb2_test(suite, "read-only",
2554 test_durable_open_read_only);
2556 suite->description = talloc_strdup(suite, "SMB2-DURABLE-OPEN tests");
2558 return suite;
2561 struct torture_suite *torture_smb2_durable_open_disconnect_init(void)
2563 struct torture_suite *suite =
2564 torture_suite_create(talloc_autofree_context(),
2565 "durable-open-disconnect");
2567 torture_suite_add_1smb2_test(suite, "open-oplock-disconnect",
2568 test_durable_open_oplock_disconnect);
2570 suite->description = talloc_strdup(suite,
2571 "SMB2-DURABLE-OPEN-DISCONNECT tests");
2573 return suite;