/* basic testing of change notify on directories */ static bool torture_smb2_notify_disabled(struct torture_context *torture, struct smb2_tree *tree1) { bool ret = true; NTSTATUS status; union smb_notify notify; union smb_open io; struct smb2_handle h1; struct smb2_request *req; torture_comment(torture, "TESTING CHANGE NOTIFY DISABLED\n"); smb2_deltree(tree1, BASEDIR); smb2_util_rmdir(tree1, BASEDIR); /* get a handle on the directory */ ZERO_STRUCT(io.smb2); io.generic.level = RAW_OPEN_SMB2; io.smb2.in.create_flags = 0; io.smb2.in.desired_access = SEC_FILE_ALL; io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL; io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE; io.smb2.in.alloc_size = 0; io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE; io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS; io.smb2.in.security_flags = 0; io.smb2.in.fname = BASEDIR; status = smb2_create(tree1, torture, &(io.smb2)); torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OK, ret, done, "smb2_create"); h1 = io.smb2.out.file.handle; ZERO_STRUCT(notify.smb2); notify.smb2.level = RAW_NOTIFY_SMB2; notify.smb2.in.buffer_size = 1000; notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; notify.smb2.in.file.handle = h1; notify.smb2.in.recursive = true; req = smb2_notify_send(tree1, &(notify.smb2)); status = smb2_notify_recv(req, torture, &(notify.smb2)); torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_NOT_IMPLEMENTED, ret, done, "smb2_notify_recv"); status = smb2_util_close(tree1, h1); torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OK, ret, done, "smb2_create"); done: smb2_deltree(tree1, BASEDIR); return ret; }
static BOOL test_valid_request(TALLOC_CTX *mem_ctx, struct smb2_tree *tree) { BOOL ret = True; NTSTATUS status; struct smb2_handle dh; struct smb2_notify n; struct smb2_request *req; status = smb2_util_roothandle(tree, &dh); CHECK_STATUS(status, NT_STATUS_OK); n.in.recursive = 0x0000; n.in.buffer_size = 0x00080000; n.in.file.handle = dh; n.in.completion_filter = 0x00000FFF; n.in.unknown = 0x00000000; req = smb2_notify_send(tree, &n); while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { if (event_loop_once(req->transport->socket->event.ctx) != 0) { break; } } status = torture_setup_complex_file(tree, FNAME); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_notify_recv(req, mem_ctx, &n); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(n.out.num_changes, 1); CHECK_VALUE(n.out.changes[0].action, NOTIFY_ACTION_REMOVED); CHECK_WIRE_STR(n.out.changes[0].name, FNAME); /* * if the change response doesn't fit in the buffer * NOTIFY_ENUM_DIR is returned. */ n.in.buffer_size = 0x00000000; req = smb2_notify_send(tree, &n); while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { if (event_loop_once(req->transport->socket->event.ctx) != 0) { break; } } status = torture_setup_complex_file(tree, FNAME); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_notify_recv(req, mem_ctx, &n); CHECK_STATUS(status, STATUS_NOTIFY_ENUM_DIR); /* * if the change response fits in the buffer we get * NT_STATUS_OK again */ n.in.buffer_size = 0x00080000; req = smb2_notify_send(tree, &n); while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { if (event_loop_once(req->transport->socket->event.ctx) != 0) { break; } } status = torture_setup_complex_file(tree, FNAME); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_notify_recv(req, mem_ctx, &n); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(n.out.num_changes, 3); CHECK_VALUE(n.out.changes[0].action, NOTIFY_ACTION_REMOVED); CHECK_WIRE_STR(n.out.changes[0].name, FNAME); CHECK_VALUE(n.out.changes[1].action, NOTIFY_ACTION_ADDED); CHECK_WIRE_STR(n.out.changes[1].name, FNAME); CHECK_VALUE(n.out.changes[2].action, NOTIFY_ACTION_MODIFIED); CHECK_WIRE_STR(n.out.changes[2].name, FNAME); /* if the first notify returns NOTIFY_ENUM_DIR, all do */ status = smb2_util_close(tree, dh); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_roothandle(tree, &dh); CHECK_STATUS(status, NT_STATUS_OK); n.in.recursive = 0x0000; n.in.buffer_size = 0x00000001; n.in.file.handle = dh; n.in.completion_filter = 0x00000FFF; n.in.unknown = 0x00000000; req = smb2_notify_send(tree, &n); while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { if (event_loop_once(req->transport->socket->event.ctx) != 0) { break; } } status = torture_setup_complex_file(tree, FNAME); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_notify_recv(req, mem_ctx, &n); CHECK_STATUS(status, STATUS_NOTIFY_ENUM_DIR); n.in.buffer_size = 0x00080000; req = smb2_notify_send(tree, &n); while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { if (event_loop_once(req->transport->socket->event.ctx) != 0) { break; } } status = torture_setup_complex_file(tree, FNAME); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_notify_recv(req, mem_ctx, &n); CHECK_STATUS(status, STATUS_NOTIFY_ENUM_DIR); /* if the buffer size is too large, we get invalid parameter */ n.in.recursive = 0x0000; n.in.buffer_size = 0x00080001; n.in.file.handle = dh; n.in.completion_filter = 0x00000FFF; n.in.unknown = 0x00000000; req = smb2_notify_send(tree, &n); status = smb2_notify_recv(req, mem_ctx, &n); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); done: return ret; }
/* Send a compound request where we expect the last request (Create, Notify) * to go asynchronous. This works against a Win7 server and the reply is * sent in two different packets. */ static bool test_compound_interim1(struct torture_context *tctx, struct smb2_tree *tree) { struct smb2_handle hd; struct smb2_create cr; NTSTATUS status = NT_STATUS_OK; const char *dname = "compound_interim_dir"; struct smb2_notify nt; bool ret = true; struct smb2_request *req[2]; /* Win7 compound request implementation deviates substantially from the * SMB2 spec as noted in MS-SMB2 <159>, <162>. This, test currently * verifies the Windows behavior, not the general spec behavior. */ smb2_transport_credits_ask_num(tree->session->transport, 5); smb2_deltree(tree, dname); smb2_transport_credits_ask_num(tree->session->transport, 1); ZERO_STRUCT(cr); cr.in.desired_access = SEC_RIGHTS_FILE_ALL; cr.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; cr.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; cr.in.share_access = NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE | NTCREATEX_SHARE_ACCESS_DELETE; cr.in.create_disposition = NTCREATEX_DISP_CREATE; cr.in.fname = dname; smb2_transport_compound_start(tree->session->transport, 2); req[0] = smb2_create_send(tree, &cr); smb2_transport_compound_set_related(tree->session->transport, true); hd.data[0] = UINT64_MAX; hd.data[1] = UINT64_MAX; ZERO_STRUCT(nt); nt.in.recursive = true; nt.in.buffer_size = 0x1000; nt.in.file.handle = hd; nt.in.completion_filter = FILE_NOTIFY_CHANGE_NAME; nt.in.unknown = 0x00000000; req[1] = smb2_notify_send(tree, &nt); status = smb2_create_recv(req[0], tree, &cr); CHECK_STATUS(status, NT_STATUS_OK); smb2_cancel(req[1]); status = smb2_notify_recv(req[1], tree, &nt); CHECK_STATUS(status, NT_STATUS_CANCELLED); smb2_util_close(tree, cr.out.file.handle); smb2_deltree(tree, dname); done: return ret; }