2 Unix SMB/CIFS implementation.
4 test suite for SMB2 durable opens
6 Copyright (C) Stefan Metzmacher 2008
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "libcli/smb2/smb2.h"
24 #include "libcli/smb2/smb2_calls.h"
25 #include "torture/torture.h"
26 #include "torture/smb2/proto.h"
28 #define CHECK_VAL(v, correct) do { \
29 if ((v) != (correct)) { \
30 torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \
31 __location__, #v, (int)v, (int)correct); \
35 #define CHECK_STATUS(status, correct) do { \
36 if (!NT_STATUS_EQUAL(status, correct)) { \
37 torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \
38 nt_errstr(status), nt_errstr(correct)); \
43 #define CHECK_CREATED(__io, __created, __attribute) \
45 CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \
46 CHECK_VAL((__io)->out.alloc_size, 0); \
47 CHECK_VAL((__io)->out.size, 0); \
48 CHECK_VAL((__io)->out.file_attr, (__attribute)); \
49 CHECK_VAL((__io)->out.reserved2, 0); \
53 basic testing of SMB2 durable opens
54 regarding the position information on the handle
56 bool test_durable_open_file_position(struct torture_context *tctx,
57 struct smb2_tree *tree1,
58 struct smb2_tree *tree2)
60 TALLOC_CTX *mem_ctx = talloc_new(tctx);
61 struct smb2_handle h1, h2;
62 struct smb2_create io1, io2;
64 const char *fname = "durable_open_position.dat";
65 union smb_fileinfo qfinfo;
66 union smb_setfileinfo sfinfo;
70 smb2_util_unlink(tree1, fname);
73 io1.in.security_flags = 0x00;
74 io1.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
75 io1.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION;
76 io1.in.create_flags = 0x00000000;
77 io1.in.reserved = 0x00000000;
78 io1.in.desired_access = SEC_RIGHTS_FILE_ALL;
79 io1.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
80 io1.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
81 NTCREATEX_SHARE_ACCESS_WRITE |
82 NTCREATEX_SHARE_ACCESS_DELETE;
83 io1.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
84 io1.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY |
85 NTCREATEX_OPTIONS_ASYNC_ALERT |
86 NTCREATEX_OPTIONS_NON_DIRECTORY_FILE |
88 io1.in.durable_open = true;
91 status = smb2_create(tree1, mem_ctx, &io1);
92 CHECK_STATUS(status, NT_STATUS_OK);
93 h1 = io1.out.file.handle;
94 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
95 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
97 /* TODO: check extra blob content */
100 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
101 qfinfo.generic.in.file.handle = h1;
102 status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo);
103 CHECK_STATUS(status, NT_STATUS_OK);
104 CHECK_VAL(qfinfo.position_information.out.position, 0);
105 pos = qfinfo.position_information.out.position;
106 torture_comment(tctx, "position: %llu\n",
107 (unsigned long long)pos);
110 sfinfo.generic.level = RAW_SFILEINFO_POSITION_INFORMATION;
111 sfinfo.generic.in.file.handle = h1;
112 sfinfo.position_information.in.position = 0x1000;
113 status = smb2_setinfo_file(tree1, &sfinfo);
114 CHECK_STATUS(status, NT_STATUS_OK);
117 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
118 qfinfo.generic.in.file.handle = h1;
119 status = smb2_getinfo_file(tree1, mem_ctx, &qfinfo);
120 CHECK_STATUS(status, NT_STATUS_OK);
121 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
122 pos = qfinfo.position_information.out.position;
123 torture_comment(tctx, "position: %llu\n",
124 (unsigned long long)pos);
130 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
131 qfinfo.generic.in.file.handle = h1;
132 status = smb2_getinfo_file(tree2, mem_ctx, &qfinfo);
133 CHECK_STATUS(status, NT_STATUS_FILE_CLOSED);
136 io2.in.fname = fname;
137 io2.in.durable_handle = &h1;
139 status = smb2_create(tree2, mem_ctx, &io2);
140 CHECK_STATUS(status, NT_STATUS_OK);
141 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
142 CHECK_VAL(io2.out.reserved, 0x00);
143 CHECK_VAL(io2.out.create_action, NTCREATEX_ACTION_EXISTED);
144 CHECK_VAL(io2.out.alloc_size, 0);
145 CHECK_VAL(io2.out.size, 0);
146 CHECK_VAL(io2.out.file_attr, FILE_ATTRIBUTE_ARCHIVE);
147 CHECK_VAL(io2.out.reserved2, 0);
149 h2 = io2.out.file.handle;
152 qfinfo.generic.level = RAW_FILEINFO_POSITION_INFORMATION;
153 qfinfo.generic.in.file.handle = h2;
154 status = smb2_getinfo_file(tree2, mem_ctx, &qfinfo);
155 CHECK_STATUS(status, NT_STATUS_OK);
156 CHECK_VAL(qfinfo.position_information.out.position, 0x1000);
157 pos = qfinfo.position_information.out.position;
158 torture_comment(tctx, "position: %llu\n",
159 (unsigned long long)pos);
161 smb2_util_close(tree2, h2);
163 talloc_free(mem_ctx);
165 smb2_util_unlink(tree2, fname);
174 Open, disconnect, oplock break, reconnect.
176 bool test_durable_open_oplock(struct torture_context *tctx,
177 struct smb2_tree *tree1,
178 struct smb2_tree *tree2)
180 TALLOC_CTX *mem_ctx = talloc_new(tctx);
181 struct smb2_create io1, io2;
182 struct smb2_handle h1, h2;
187 /* Choose a random name in case the state is left a little funky. */
188 snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8));
191 smb2_util_unlink(tree1, fname);
193 /* Create with batch oplock */
195 io1.in.security_flags = 0x00;
196 io1.in.oplock_level = SMB2_OPLOCK_LEVEL_BATCH;
197 io1.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION;
198 io1.in.create_flags = 0x00000000;
199 io1.in.reserved = 0x00000000;
200 io1.in.desired_access = SEC_RIGHTS_FILE_ALL;
201 io1.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
202 io1.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
203 NTCREATEX_SHARE_ACCESS_WRITE |
204 NTCREATEX_SHARE_ACCESS_DELETE;
205 io1.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
206 io1.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY |
207 NTCREATEX_OPTIONS_ASYNC_ALERT |
208 NTCREATEX_OPTIONS_NON_DIRECTORY_FILE |
210 io1.in.fname = fname;
211 io1.in.durable_open = true;
214 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
216 status = smb2_create(tree1, mem_ctx, &io1);
217 CHECK_STATUS(status, NT_STATUS_OK);
218 h1 = io1.out.file.handle;
219 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
220 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
222 /* Disconnect after getting the batch */
227 * Windows7 (build 7000) will break a batch oplock immediately if the
228 * original client is gone. (ZML: This seems like a bug. It should give
229 * some time for the client to reconnect!)
231 status = smb2_create(tree2, mem_ctx, &io2);
232 CHECK_STATUS(status, NT_STATUS_OK);
233 h2 = io2.out.file.handle;
234 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
235 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_BATCH);
237 /* What if tree1 tries to come back and reclaim? */
238 if (!torture_smb2_connection(tctx, &tree1)) {
239 torture_warning(tctx, "couldn't reconnect, bailing\n");
245 io1.in.fname = fname;
246 io1.in.durable_handle = &h1;
248 status = smb2_create(tree1, mem_ctx, &io1);
249 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
252 smb2_util_close(tree2, h2);
253 smb2_util_unlink(tree2, fname);
262 Open, disconnect, lease break, reconnect.
264 bool test_durable_open_lease(struct torture_context *tctx,
265 struct smb2_tree *tree1,
266 struct smb2_tree *tree2)
268 TALLOC_CTX *mem_ctx = talloc_new(tctx);
269 struct smb2_create io1, io2;
270 struct smb2_lease ls1, ls2;
271 struct smb2_handle h1, h2;
275 uint64_t lease1, lease2;
278 * Choose a random name and random lease in case the state is left a
283 snprintf(fname, 256, "durable_open_lease_%s.dat", generate_random_str(tctx, 8));
286 smb2_util_unlink(tree1, fname);
288 /* Create with lease */
290 io1.in.security_flags = 0x00;
291 io1.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
292 io1.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION;
293 io1.in.create_flags = 0x00000000;
294 io1.in.reserved = 0x00000000;
295 io1.in.desired_access = SEC_RIGHTS_FILE_ALL;
296 io1.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
297 io1.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
298 NTCREATEX_SHARE_ACCESS_WRITE |
299 NTCREATEX_SHARE_ACCESS_DELETE;
300 io1.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
301 io1.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY |
302 NTCREATEX_OPTIONS_ASYNC_ALERT |
303 NTCREATEX_OPTIONS_NON_DIRECTORY_FILE |
305 io1.in.fname = fname;
306 io1.in.durable_open = true;
309 ls1.lease_key.data[0] = lease1;
310 ls1.lease_key.data[1] = ~lease1;
311 ls1.lease_state = SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE;
312 io1.in.lease_request = &ls1;
316 ls2.lease_key.data[0] = lease2;
317 ls2.lease_key.data[1] = ~lease2;
318 io2.in.lease_request = &ls2;
319 io2.in.create_disposition = NTCREATEX_DISP_OPEN;
321 status = smb2_create(tree1, mem_ctx, &io1);
322 CHECK_STATUS(status, NT_STATUS_OK);
323 h1 = io1.out.file.handle;
324 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
326 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
327 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease1);
328 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease1);
329 CHECK_VAL(io1.out.lease_response.lease_state,
330 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
332 /* Disconnect after getting the lease */
337 * Windows7 (build 7000) will grant an RH lease immediate (not an RHW?)
338 * even if the original client is gone. (ZML: This seems like a bug. It
339 * should give some time for the client to reconnect! And why RH?)
341 status = smb2_create(tree2, mem_ctx, &io2);
342 CHECK_STATUS(status, NT_STATUS_OK);
343 h2 = io2.out.file.handle;
344 CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
346 CHECK_VAL(io2.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
347 CHECK_VAL(io2.out.lease_response.lease_key.data[0], lease2);
348 CHECK_VAL(io2.out.lease_response.lease_key.data[1], ~lease2);
349 CHECK_VAL(io2.out.lease_response.lease_state,
350 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
352 /* What if tree1 tries to come back and reclaim? */
353 if (!torture_smb2_connection(tctx, &tree1)) {
354 torture_warning(tctx, "couldn't reconnect, bailing\n");
360 io1.in.fname = fname;
361 io1.in.durable_handle = &h1;
362 io1.in.lease_request = &ls1;
364 status = smb2_create(tree1, mem_ctx, &io1);
365 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
368 smb2_util_close(tree2, h2);
369 smb2_util_unlink(tree2, fname);
378 Open, take BRL, disconnect, reconnect.
380 bool test_durable_open_lock(struct torture_context *tctx,
381 struct smb2_tree *tree)
383 TALLOC_CTX *mem_ctx = talloc_new(tctx);
384 struct smb2_create io;
385 struct smb2_lease ls;
386 struct smb2_handle h;
387 struct smb2_lock lck;
388 struct smb2_lock_element el[2];
395 * Choose a random name and random lease in case the state is left a
399 snprintf(fname, 256, "durable_open_lock_%s.dat", generate_random_str(tctx, 8));
402 smb2_util_unlink(tree, fname);
404 /* Create with lease */
406 io.in.security_flags = 0x00;
407 io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
408 io.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION;
409 io.in.create_flags = 0x00000000;
410 io.in.reserved = 0x00000000;
411 io.in.desired_access = SEC_RIGHTS_FILE_ALL;
412 io.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
413 io.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
414 NTCREATEX_SHARE_ACCESS_WRITE |
415 NTCREATEX_SHARE_ACCESS_DELETE;
416 io.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
417 io.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY |
418 NTCREATEX_OPTIONS_ASYNC_ALERT |
419 NTCREATEX_OPTIONS_NON_DIRECTORY_FILE |
422 io.in.durable_open = true;
425 ls.lease_key.data[0] = lease;
426 ls.lease_key.data[1] = ~lease;
427 ls.lease_state = SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE;
428 io.in.lease_request = &ls;
430 status = smb2_create(tree, mem_ctx, &io);
431 CHECK_STATUS(status, NT_STATUS_OK);
432 h = io.out.file.handle;
433 CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
435 CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
436 CHECK_VAL(io.out.lease_response.lease_key.data[0], lease);
437 CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease);
438 CHECK_VAL(io.out.lease_response.lease_state,
439 SMB2_LEASE_READ|SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
444 lck.in.lock_count = 0x0001;
445 lck.in.lock_sequence = 0x00000000;
446 lck.in.file.handle = h;
449 el[0].reserved = 0x00000000;
450 el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE;
451 status = smb2_lock(tree, &lck);
452 CHECK_STATUS(status, NT_STATUS_OK);
454 /* Disconnect/Reconnect. */
458 if (!torture_smb2_connection(tctx, &tree)) {
459 torture_warning(tctx, "couldn't reconnect, bailing\n");
466 io.in.durable_handle = &h;
467 io.in.lease_request = &ls;
469 status = smb2_create(tree, mem_ctx, &io);
470 CHECK_STATUS(status, NT_STATUS_OK);
471 h = io.out.file.handle;
473 lck.in.file.handle = h;
474 el[0].flags = SMB2_LOCK_FLAG_UNLOCK;
475 status = smb2_lock(tree, &lck);
476 CHECK_STATUS(status, NT_STATUS_OK);
479 smb2_util_close(tree, h);
480 smb2_util_unlink(tree, fname);
487 Open, disconnect, open in another tree, reconnect.
489 This test actually demonstrates a minimum level of respect for the durable
490 open in the face of another open. As long as this test shows an inability to
491 reconnect after an open, the oplock/lease tests above will certainly
492 demonstrate an error on reconnect.
494 bool test_durable_open_open(struct torture_context *tctx,
495 struct smb2_tree *tree1,
496 struct smb2_tree *tree2)
498 TALLOC_CTX *mem_ctx = talloc_new(tctx);
499 struct smb2_create io1, io2;
500 struct smb2_lease ls;
501 struct smb2_handle h1, h2;
508 * Choose a random name and random lease in case the state is left a
512 snprintf(fname, 256, "durable_open_lock_%s.dat", generate_random_str(tctx, 8));
515 smb2_util_unlink(tree1, fname);
517 /* Create with lease */
519 io1.in.security_flags = 0x00;
520 io1.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
521 io1.in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION;
522 io1.in.create_flags = 0x00000000;
523 io1.in.reserved = 0x00000000;
524 io1.in.desired_access = SEC_RIGHTS_FILE_ALL;
525 io1.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
526 io1.in.share_access = NTCREATEX_SHARE_ACCESS_NONE;
527 io1.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
528 io1.in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY |
529 NTCREATEX_OPTIONS_ASYNC_ALERT |
530 NTCREATEX_OPTIONS_NON_DIRECTORY_FILE |
532 io1.in.fname = fname;
533 io1.in.durable_open = true;
536 io2.in.oplock_level = SMB2_OPLOCK_LEVEL_NONE;
537 io2.in.durable_open = false;
540 ls.lease_key.data[0] = lease;
541 ls.lease_key.data[1] = ~lease;
542 ls.lease_state = SMB2_LEASE_READ|SMB2_LEASE_HANDLE;
543 io1.in.lease_request = &ls;
545 status = smb2_create(tree1, mem_ctx, &io1);
546 CHECK_STATUS(status, NT_STATUS_OK);
547 h1 = io1.out.file.handle;
548 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
550 CHECK_VAL(io1.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE);
551 CHECK_VAL(io1.out.lease_response.lease_key.data[0], lease);
552 CHECK_VAL(io1.out.lease_response.lease_key.data[1], ~lease);
553 CHECK_VAL(io1.out.lease_response.lease_state,
554 SMB2_LEASE_READ|SMB2_LEASE_HANDLE);
560 /* Open the file in tree2 */
561 status = smb2_create(tree2, mem_ctx, &io2);
562 CHECK_STATUS(status, NT_STATUS_OK);
563 h2 = io2.out.file.handle;
564 CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
567 if (!torture_smb2_connection(tctx, &tree1)) {
568 torture_warning(tctx, "couldn't reconnect, bailing\n");
574 io1.in.fname = fname;
575 io1.in.durable_handle = &h1;
576 io1.in.lease_request = &ls;
579 * Windows7 (build 7000) will give away an open immediately if the
580 * original client is gone. (ZML: This seems like a bug. It should give
581 * some time for the client to reconnect!)
583 status = smb2_create(tree1, mem_ctx, &io1);
584 CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
585 h1 = io1.out.file.handle;
588 smb2_util_close(tree2, h2);
589 smb2_util_unlink(tree2, fname);
590 smb2_util_close(tree1, h1);
591 smb2_util_unlink(tree1, fname);
599 struct torture_suite *torture_smb2_durable_open_init(void)
601 struct torture_suite *suite =
602 torture_suite_create(talloc_autofree_context(), "durable-open");
604 torture_suite_add_2smb2_test(suite, "file-position",
605 test_durable_open_file_position);
606 torture_suite_add_2smb2_test(suite, "oplock", test_durable_open_oplock);
607 torture_suite_add_2smb2_test(suite, "lease", test_durable_open_lease);
608 torture_suite_add_1smb2_test(suite, "lock", test_durable_open_lock);
609 torture_suite_add_2smb2_test(suite, "open", test_durable_open_open);
611 suite->description = talloc_strdup(suite, "SMB2-DURABLE-OPEN tests");