diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source4/torture/smb2/util.c | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/torture/smb2/util.c')
-rw-r--r-- | source4/torture/smb2/util.c | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/source4/torture/smb2/util.c b/source4/torture/smb2/util.c new file mode 100644 index 0000000..233f589 --- /dev/null +++ b/source4/torture/smb2/util.c @@ -0,0 +1,1045 @@ +/* + Unix SMB/CIFS implementation. + + helper functions for SMB2 test suite + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/security/security_descriptor.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "../libcli/smb/smbXcli_base.h" +#include "lib/cmdline/cmdline.h" +#include "system/time.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" +#include "lib/util/tevent_ntstatus.h" + +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "source4/torture/util.h" +#include "libcli/security/dom_sid.h" +#include "librpc/gen_ndr/lsa.h" +#include "libcli/util/clilsa.h" + + +/* + write to a file on SMB2 +*/ +NTSTATUS smb2_util_write(struct smb2_tree *tree, + struct smb2_handle handle, + const void *buf, off_t offset, size_t size) +{ + struct smb2_write w; + + ZERO_STRUCT(w); + w.in.file.handle = handle; + w.in.offset = offset; + w.in.data = data_blob_const(buf, size); + + return smb2_write(tree, &w); +} + +/* + create a complex file/dir using the SMB2 protocol +*/ +static NTSTATUS smb2_create_complex(struct torture_context *tctx, + struct smb2_tree *tree, + const char *fname, + struct smb2_handle *handle, + bool dir) +{ + TALLOC_CTX *tmp_ctx = talloc_new(tree); + char buf[7] = "abc"; + struct smb2_create io; + union smb_setfileinfo setfile; + union smb_fileinfo fileinfo; + time_t t = (time(NULL) & ~1); + NTSTATUS status; + + smb2_util_unlink(tree, fname); + ZERO_STRUCT(io); + io.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = fname; + if (dir) { + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.share_access &= ~NTCREATEX_SHARE_ACCESS_DELETE; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_CREATE; + } + + /* it seems vista is now fussier about alignment? */ + if (strchr(fname, ':') == NULL) { + /* setup some EAs */ + io.in.eas.num_eas = 2; + io.in.eas.eas = talloc_array(tmp_ctx, struct ea_struct, 2); + io.in.eas.eas[0].flags = 0; + io.in.eas.eas[0].name.s = "EAONE"; + io.in.eas.eas[0].value = data_blob_talloc(tmp_ctx, "VALUE1", 6); + io.in.eas.eas[1].flags = 0; + io.in.eas.eas[1].name.s = "SECONDEA"; + io.in.eas.eas[1].value = data_blob_talloc(tmp_ctx, "ValueTwo", 8); + } + + status = smb2_create(tree, tmp_ctx, &io); + if (NT_STATUS_EQUAL(status, NT_STATUS_EAS_NOT_SUPPORTED)) { + torture_comment( + tctx, "EAs not supported, creating: %s\n", fname); + io.in.eas.num_eas = 0; + status = smb2_create(tree, tmp_ctx, &io); + } + + talloc_free(tmp_ctx); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + if (!dir) { + status = smb2_util_write(tree, *handle, buf, 0, sizeof(buf)); + NT_STATUS_NOT_OK_RETURN(status); + } + + /* make sure all the timestamps aren't the same, and are also + in different DST zones*/ + setfile.generic.level = RAW_SFILEINFO_BASIC_INFORMATION; + setfile.generic.in.file.handle = *handle; + + unix_to_nt_time(&setfile.basic_info.in.create_time, t + 9*30*24*60*60); + unix_to_nt_time(&setfile.basic_info.in.access_time, t + 6*30*24*60*60); + unix_to_nt_time(&setfile.basic_info.in.write_time, t + 3*30*24*60*60); + unix_to_nt_time(&setfile.basic_info.in.change_time, t + 1*30*24*60*60); + setfile.basic_info.in.attrib = FILE_ATTRIBUTE_NORMAL; + + status = smb2_setinfo_file(tree, &setfile); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to setup file times - %s\n", nt_errstr(status)); + return status; + } + + /* make sure all the timestamps aren't the same */ + fileinfo.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + fileinfo.generic.in.file.handle = *handle; + + status = smb2_getinfo_file(tree, tree, &fileinfo); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to query file times - %s\n", nt_errstr(status)); + return status; + + } + +#define CHECK_TIME(field) do {\ + if (setfile.basic_info.in.field != fileinfo.all_info2.out.field) { \ + torture_comment(tctx, "(%s) " #field " not setup correctly: %s(%llu) => %s(%llu)\n", \ + __location__, \ + nt_time_string(tree, setfile.basic_info.in.field), \ + (unsigned long long)setfile.basic_info.in.field, \ + nt_time_string(tree, fileinfo.basic_info.out.field), \ + (unsigned long long)fileinfo.basic_info.out.field); \ + status = NT_STATUS_INVALID_PARAMETER; \ + } \ +} while (0) + + CHECK_TIME(create_time); + CHECK_TIME(access_time); + CHECK_TIME(write_time); + CHECK_TIME(change_time); + + return status; +} + +/* + create a complex file using the SMB2 protocol +*/ +NTSTATUS smb2_create_complex_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return smb2_create_complex(tctx, tree, fname, handle, false); +} + +/* + create a complex dir using the SMB2 protocol +*/ +NTSTATUS smb2_create_complex_dir(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return smb2_create_complex(tctx, tree, fname, handle, true); +} + +/* + show lots of information about a file +*/ +void torture_smb2_all_info(struct torture_context *tctx, + struct smb2_tree *tree, struct smb2_handle handle) +{ + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + union smb_fileinfo io; + + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = handle; + + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("getinfo failed - %s\n", nt_errstr(status))); + talloc_free(tmp_ctx); + return; + } + + torture_comment(tctx, "all_info for '%s'\n", io.all_info2.out.fname.s); + torture_comment(tctx, "\tcreate_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.create_time)); + torture_comment(tctx, "\taccess_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.access_time)); + torture_comment(tctx, "\twrite_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.write_time)); + torture_comment(tctx, "\tchange_time: %s\n", nt_time_string(tmp_ctx, io.all_info2.out.change_time)); + torture_comment(tctx, "\tattrib: 0x%x\n", io.all_info2.out.attrib); + torture_comment(tctx, "\tunknown1: 0x%x\n", io.all_info2.out.unknown1); + torture_comment(tctx, "\talloc_size: %llu\n", (long long)io.all_info2.out.alloc_size); + torture_comment(tctx, "\tsize: %llu\n", (long long)io.all_info2.out.size); + torture_comment(tctx, "\tnlink: %u\n", io.all_info2.out.nlink); + torture_comment(tctx, "\tdelete_pending: %u\n", io.all_info2.out.delete_pending); + torture_comment(tctx, "\tdirectory: %u\n", io.all_info2.out.directory); + torture_comment(tctx, "\tfile_id: %llu\n", (long long)io.all_info2.out.file_id); + torture_comment(tctx, "\tea_size: %u\n", io.all_info2.out.ea_size); + torture_comment(tctx, "\taccess_mask: 0x%08x\n", io.all_info2.out.access_mask); + torture_comment(tctx, "\tposition: 0x%llx\n", (long long)io.all_info2.out.position); + torture_comment(tctx, "\tmode: 0x%llx\n", (long long)io.all_info2.out.mode); + + /* short name, if any */ + io.generic.level = RAW_FILEINFO_ALT_NAME_INFORMATION; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "\tshort name: '%s'\n", io.alt_name_info.out.fname.s); + } + + /* the EAs, if any */ + io.generic.level = RAW_FILEINFO_SMB2_ALL_EAS; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + int i; + for (i=0;i<io.all_eas.out.num_eas;i++) { + torture_comment(tctx, "\tEA[%d] flags=%d len=%d '%s'\n", i, + io.all_eas.out.eas[i].flags, + (int)io.all_eas.out.eas[i].value.length, + io.all_eas.out.eas[i].name.s); + } + } + + /* streams, if available */ + io.generic.level = RAW_FILEINFO_STREAM_INFORMATION; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + int i; + for (i=0;i<io.stream_info.out.num_streams;i++) { + torture_comment(tctx, "\tstream %d:\n", i); + torture_comment(tctx, "\t\tsize %ld\n", + (long)io.stream_info.out.streams[i].size); + torture_comment(tctx, "\t\talloc size %ld\n", + (long)io.stream_info.out.streams[i].alloc_size); + torture_comment(tctx, "\t\tname %s\n", io.stream_info.out.streams[i].stream_name.s); + } + } + + if (DEBUGLVL(1)) { + /* the security descriptor */ + io.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + io.query_secdesc.in.secinfo_flags = + SECINFO_OWNER|SECINFO_GROUP| + SECINFO_DACL; + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(security_descriptor, io.query_secdesc.out.sd); + } + } + + talloc_free(tmp_ctx); +} + +/* + get granted access of a file handle +*/ +NTSTATUS torture_smb2_get_allinfo_access(struct smb2_tree *tree, + struct smb2_handle handle, + uint32_t *granted_access) +{ + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tree); + union smb_fileinfo io; + + io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + io.generic.in.file.handle = handle; + + status = smb2_getinfo_file(tree, tmp_ctx, &io); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("getinfo failed - %s\n", nt_errstr(status))); + goto out; + } + + *granted_access = io.all_info2.out.access_mask; + +out: + talloc_free(tmp_ctx); + return status; +} + +/** + * open a smb2 tree connect + */ +bool torture_smb2_tree_connect(struct torture_context *tctx, + struct smb2_session *session, + TALLOC_CTX *mem_ctx, + struct smb2_tree **_tree) +{ + NTSTATUS status; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + const char *unc; + struct smb2_tree *tree; + struct tevent_req *subreq; + uint32_t timeout_msec; + + unc = talloc_asprintf(tctx, "\\\\%s\\%s", host, share); + torture_assert(tctx, unc != NULL, "talloc_asprintf"); + + tree = smb2_tree_init(session, mem_ctx, false); + torture_assert(tctx, tree != NULL, "smb2_tree_init"); + + timeout_msec = session->transport->options.request_timeout * 1000; + + subreq = smb2cli_tcon_send(tree, tctx->ev, + session->transport->conn, + timeout_msec, + session->smbXcli, + tree->smbXcli, + 0, /* flags */ + unc); + torture_assert(tctx, subreq != NULL, "smb2cli_tcon_send"); + + torture_assert(tctx, + tevent_req_poll_ntstatus(subreq, tctx->ev, &status), + "tevent_req_poll_ntstatus"); + + status = smb2cli_tcon_recv(subreq); + TALLOC_FREE(subreq); + torture_assert_ntstatus_ok(tctx, status, "smb2cli_tcon_recv"); + + *_tree = tree; + + return true; +} + +/** + * do a smb2 session setup (without a tree connect) + */ +bool torture_smb2_session_setup(struct torture_context *tctx, + struct smb2_transport *transport, + uint64_t previous_session_id, + TALLOC_CTX *mem_ctx, + struct smb2_session **_session) +{ + NTSTATUS status; + struct smb2_session *session; + + session = smb2_session_init(transport, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + mem_ctx); + + if (session == NULL) { + return false; + } + + status = smb2_session_setup_spnego(session, + samba_cmdline_get_creds(), + previous_session_id); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "session setup failed: %s\n", nt_errstr(status)); + talloc_free(session); + return false; + } + + *_session = session; + + return true; +} + +/* + open a smb2 connection +*/ +bool torture_smb2_connection_ext(struct torture_context *tctx, + uint64_t previous_session_id, + const struct smbcli_options *options, + struct smb2_tree **tree) +{ + NTSTATUS status; + const char *host = torture_setting_string(tctx, "host", NULL); + const char *share = torture_setting_string(tctx, "share", NULL); + const char *p = torture_setting_string(tctx, "unclist", NULL); + TALLOC_CTX *mem_ctx = NULL; + bool ok; + + if (p != NULL) { + char *host2 = NULL; + char *share2 = NULL; + + mem_ctx = talloc_new(tctx); + if (mem_ctx == NULL) { + return false; + } + + ok = torture_get_conn_index(tctx->conn_index++, mem_ctx, tctx, + &host2, &share2); + if (!ok) { + TALLOC_FREE(mem_ctx); + return false; + } + + host = host2; + share = share2; + } + + status = smb2_connect_ext(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + NULL, /* existing_conn */ + previous_session_id, + tree, + tctx->ev, + options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to connect to SMB2 share \\\\%s\\%s - %s\n", + host, share, nt_errstr(status)); + TALLOC_FREE(mem_ctx); + return false; + } + + TALLOC_FREE(mem_ctx); + return true; +} + +bool torture_smb2_connection(struct torture_context *tctx, struct smb2_tree **tree) +{ + bool ret; + struct smbcli_options options; + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + ret = torture_smb2_connection_ext(tctx, 0, &options, tree); + + return ret; +} + +/** + * SMB2 connect with share from soption + **/ +bool torture_smb2_con_share(struct torture_context *tctx, + const char *share, + struct smb2_tree **tree) +{ + struct smbcli_options options; + NTSTATUS status; + const char *host = torture_setting_string(tctx, "host", NULL); + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + status = smb2_connect(tctx, + host, + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx) + ); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to connect to SMB2 share \\\\%s\\%s - %s\n", + host, share, nt_errstr(status)); + return false; + } + return true; +} + +/** + * SMB2 connect with share from soption + **/ +bool torture_smb2_con_sopt(struct torture_context *tctx, + const char *soption, + struct smb2_tree **tree) +{ + const char *share = torture_setting_string(tctx, soption, NULL); + + if (share == NULL) { + torture_comment(tctx, "No share for option %s\n", soption); + return false; + } + + return torture_smb2_con_share(tctx, share, tree); +} + +/* + create and return a handle to a test file + with a specific access mask +*/ +NTSTATUS torture_smb2_testfile_access(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle, + uint32_t desired_access) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = desired_access; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = fname; + + status = smb2_create(tree, tree, &io); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* + create and return a handle to a test file +*/ +NTSTATUS torture_smb2_testfile(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return torture_smb2_testfile_access(tree, fname, handle, + SEC_RIGHTS_FILE_ALL); +} + +/* + create and return a handle to a test file + with a specific access mask +*/ +NTSTATUS torture_smb2_open(struct smb2_tree *tree, + const char *fname, + uint32_t desired_access, + struct smb2_handle *handle) +{ + struct smb2_create io; + NTSTATUS status; + + io = (struct smb2_create) { + .in.fname = fname, + .in.desired_access = desired_access, + .in.file_attributes = FILE_ATTRIBUTE_NORMAL, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + }; + + status = smb2_create(tree, tree, &io); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* + create and return a handle to a test directory + with specific desired access +*/ +NTSTATUS torture_smb2_testdir_access(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle, + uint32_t desired_access) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = desired_access; + io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_WRITE|NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io.in.fname = fname; + + status = smb2_create(tree, tree, &io); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* + create and return a handle to a test directory +*/ +NTSTATUS torture_smb2_testdir(struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + return torture_smb2_testdir_access(tree, fname, handle, + SEC_RIGHTS_DIR_ALL); +} + +/* + create a simple file using the SMB2 protocol +*/ +NTSTATUS smb2_create_simple_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname, + struct smb2_handle *handle) +{ + char buf[7] = "abc"; + NTSTATUS status; + + smb2_util_unlink(tree, fname); + status = torture_smb2_testfile_access(tree, + fname, handle, + SEC_FLAG_MAXIMUM_ALLOWED); + NT_STATUS_NOT_OK_RETURN(status); + + status = smb2_util_write(tree, *handle, buf, 0, sizeof(buf)); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +/* + create a simple file using SMB2. +*/ +NTSTATUS torture_setup_simple_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname) +{ + struct smb2_handle handle; + NTSTATUS status = smb2_create_simple_file(tctx, tree, fname, &handle); + NT_STATUS_NOT_OK_RETURN(status); + return smb2_util_close(tree, handle); +} + +/* + create a complex file using SMB2, to make it easier to + find fields in SMB2 getinfo levels +*/ +NTSTATUS torture_setup_complex_file(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname) +{ + struct smb2_handle handle; + NTSTATUS status = smb2_create_complex_file(tctx, tree, fname, &handle); + NT_STATUS_NOT_OK_RETURN(status); + return smb2_util_close(tree, handle); +} + + +/* + create a complex dir using SMB2, to make it easier to + find fields in SMB2 getinfo levels +*/ +NTSTATUS torture_setup_complex_dir(struct torture_context *tctx, + struct smb2_tree *tree, const char *fname) +{ + struct smb2_handle handle; + NTSTATUS status = smb2_create_complex_dir(tctx, tree, fname, &handle); + NT_STATUS_NOT_OK_RETURN(status); + return smb2_util_close(tree, handle); +} + + +/* + return a handle to the root of the share +*/ +NTSTATUS smb2_util_roothandle(struct smb2_tree *tree, struct smb2_handle *handle) +{ + struct smb2_create io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = SEC_STD_SYNCHRONIZE | SEC_DIR_READ_ATTRIBUTE | SEC_DIR_LIST; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = NTCREATEX_SHARE_ACCESS_READ|NTCREATEX_SHARE_ACCESS_DELETE; + io.in.create_options = NTCREATEX_OPTIONS_ASYNC_ALERT; + io.in.fname = ""; + + status = smb2_create(tree, tree, &io); + NT_STATUS_NOT_OK_RETURN(status); + + *handle = io.out.file.handle; + + return NT_STATUS_OK; +} + +/* Comparable to torture_setup_dir, but for SMB2. */ +bool smb2_util_setup_dir(struct torture_context *tctx, struct smb2_tree *tree, + const char *dname) +{ + NTSTATUS status; + + /* XXX: smb_raw_exit equivalent? + smb_raw_exit(cli->session); */ + if (smb2_deltree(tree, dname) == -1) { + torture_result(tctx, TORTURE_ERROR, "Unable to deltree when setting up %s.\n", dname); + return false; + } + + status = smb2_util_mkdir(tree, dname); + if (NT_STATUS_IS_ERR(status)) { + torture_result(tctx, TORTURE_ERROR, "Unable to mkdir when setting up %s - %s\n", dname, + nt_errstr(status)); + return false; + } + + return true; +} + +#define CHECK_STATUS(status, correct) do { \ + if (!NT_STATUS_EQUAL(status, correct)) { \ + torture_result(tctx, TORTURE_FAIL, "(%s) Incorrect status %s - should be %s\n", \ + __location__, nt_errstr(status), nt_errstr(correct)); \ + ret = false; \ + goto done; \ + }} while (0) + +/* + * Helper function to verify a security descriptor, by querying + * and comparing against the passed in sd. + */ +bool smb2_util_verify_sd(TALLOC_CTX *tctx, struct smb2_tree *tree, + struct smb2_handle handle, struct security_descriptor *sd) +{ + NTSTATUS status; + bool ret = true; + union smb_fileinfo q = {}; + + q.query_secdesc.level = RAW_FILEINFO_SEC_DESC; + q.query_secdesc.in.file.handle = handle; + q.query_secdesc.in.secinfo_flags = + SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + if (!security_acl_equal( + q.query_secdesc.out.sd->dacl, sd->dacl)) { + torture_warning(tctx, "%s: security descriptors don't match!\n", + __location__); + torture_warning(tctx, "got:\n"); + NDR_PRINT_DEBUG(security_descriptor, + q.query_secdesc.out.sd); + torture_warning(tctx, "expected:\n"); + NDR_PRINT_DEBUG(security_descriptor, sd); + ret = false; + } + + done: + return ret; +} + +/* + * Helper function to verify attributes, by querying + * and comparing against the passed in attrib. + */ +bool smb2_util_verify_attrib(TALLOC_CTX *tctx, struct smb2_tree *tree, + struct smb2_handle handle, uint32_t attrib) +{ + NTSTATUS status; + bool ret = true; + union smb_fileinfo q = {}; + + q.standard.level = RAW_FILEINFO_SMB2_ALL_INFORMATION; + q.standard.in.file.handle = handle; + status = smb2_getinfo_file(tree, tctx, &q); + CHECK_STATUS(status, NT_STATUS_OK); + + q.all_info2.out.attrib &= ~(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_NONINDEXED); + + if (q.all_info2.out.attrib != attrib) { + torture_warning(tctx, "%s: attributes don't match! " + "got %x, expected %x\n", __location__, + (uint32_t)q.standard.out.attrib, + (uint32_t)attrib); + ret = false; + } + + done: + return ret; +} + + +uint32_t smb2_util_lease_state(const char *ls) +{ + uint32_t val = 0; + int i; + + for (i = 0; i < strlen(ls); i++) { + switch (ls[i]) { + case 'R': + val |= SMB2_LEASE_READ; + break; + case 'H': + val |= SMB2_LEASE_HANDLE; + break; + case 'W': + val |= SMB2_LEASE_WRITE; + break; + } + } + + return val; +} + +char *smb2_util_lease_state_string(TALLOC_CTX *mem_ctx, uint32_t ls) +{ + return talloc_asprintf(mem_ctx, "0x%0x (%s%s%s)", + (unsigned)ls, + ls & SMB2_LEASE_READ ? "R": "", + ls & SMB2_LEASE_HANDLE ? "H": "", + ls & SMB2_LEASE_WRITE ? "W": ""); +} + +uint32_t smb2_util_share_access(const char *sharemode) +{ + uint32_t val = NTCREATEX_SHARE_ACCESS_NONE; /* 0 */ + int i; + + for (i = 0; i < strlen(sharemode); i++) { + switch(sharemode[i]) { + case 'R': + val |= NTCREATEX_SHARE_ACCESS_READ; + break; + case 'W': + val |= NTCREATEX_SHARE_ACCESS_WRITE; + break; + case 'D': + val |= NTCREATEX_SHARE_ACCESS_DELETE; + break; + } + } + + return val; +} + +uint8_t smb2_util_oplock_level(const char *op) +{ + uint8_t val = SMB2_OPLOCK_LEVEL_NONE; + int i; + + for (i = 0; i < strlen(op); i++) { + switch (op[i]) { + case 's': + return SMB2_OPLOCK_LEVEL_II; + case 'x': + return SMB2_OPLOCK_LEVEL_EXCLUSIVE; + case 'b': + return SMB2_OPLOCK_LEVEL_BATCH; + default: + continue; + } + } + + return val; +} + +/** + * Helper functions to fill a smb2_create struct for several + * open scenarios. + */ +void smb2_generic_create_share(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint32_t disposition, + uint32_t share_access, + uint8_t oplock, uint64_t leasekey, + uint32_t leasestate) +{ + ZERO_STRUCT(*io); + io->in.security_flags = 0x00; + io->in.oplock_level = oplock; + io->in.impersonation_level = NTCREATEX_IMPERSONATION_IMPERSONATION; + io->in.create_flags = 0x00000000; + io->in.reserved = 0x00000000; + io->in.desired_access = SEC_RIGHTS_FILE_ALL; + io->in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io->in.share_access = share_access; + io->in.create_disposition = disposition; + io->in.create_options = NTCREATEX_OPTIONS_SEQUENTIAL_ONLY | + NTCREATEX_OPTIONS_ASYNC_ALERT | + NTCREATEX_OPTIONS_NON_DIRECTORY_FILE | + 0x00200000; + io->in.fname = name; + + if (dir) { + io->in.create_options = NTCREATEX_OPTIONS_DIRECTORY; + io->in.file_attributes = FILE_ATTRIBUTE_DIRECTORY; + io->in.create_disposition = NTCREATEX_DISP_CREATE; + } + + if (ls) { + ZERO_STRUCTPN(ls); + ls->lease_key.data[0] = leasekey; + ls->lease_key.data[1] = ~leasekey; + ls->lease_state = leasestate; + io->in.lease_request = ls; + } +} + +void smb2_generic_create(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint32_t disposition, + uint8_t oplock, uint64_t leasekey, + uint32_t leasestate) +{ + smb2_generic_create_share(io, ls, dir, name, disposition, + smb2_util_share_access("RWD"), + oplock, + leasekey, leasestate); +} + +void smb2_lease_create_share(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint32_t share_access, + uint64_t leasekey, uint32_t leasestate) +{ + smb2_generic_create_share(io, ls, dir, name, NTCREATEX_DISP_OPEN_IF, + share_access, SMB2_OPLOCK_LEVEL_LEASE, + leasekey, leasestate); +} + +void smb2_lease_create(struct smb2_create *io, struct smb2_lease *ls, + bool dir, const char *name, uint64_t leasekey, + uint32_t leasestate) +{ + smb2_lease_create_share(io, ls, dir, name, + smb2_util_share_access("RWD"), + leasekey, leasestate); +} + +void smb2_lease_v2_create_share(struct smb2_create *io, + struct smb2_lease *ls, + bool dir, + const char *name, + uint32_t share_access, + uint64_t leasekey, + const uint64_t *parentleasekey, + uint32_t leasestate, + uint16_t lease_epoch) +{ + smb2_generic_create_share(io, NULL, dir, name, NTCREATEX_DISP_OPEN_IF, + share_access, SMB2_OPLOCK_LEVEL_LEASE, 0, 0); + + if (ls) { + ZERO_STRUCT(*ls); + ls->lease_key.data[0] = leasekey; + ls->lease_key.data[1] = ~leasekey; + ls->lease_state = leasestate; + if (parentleasekey != NULL) { + ls->lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET; + ls->parent_lease_key.data[0] = *parentleasekey; + ls->parent_lease_key.data[1] = ~(*parentleasekey); + } + ls->lease_epoch = lease_epoch; + io->in.lease_request_v2 = ls; + } +} + +void smb2_lease_v2_create(struct smb2_create *io, + struct smb2_lease *ls, + bool dir, + const char *name, + uint64_t leasekey, + const uint64_t *parentleasekey, + uint32_t leasestate, + uint16_t lease_epoch) +{ + smb2_lease_v2_create_share(io, ls, dir, name, + smb2_util_share_access("RWD"), + leasekey, parentleasekey, + leasestate, lease_epoch); +} + + +void smb2_oplock_create_share(struct smb2_create *io, const char *name, + uint32_t share_access, uint8_t oplock) +{ + smb2_generic_create_share(io, NULL, false, name, NTCREATEX_DISP_OPEN_IF, + share_access, oplock, 0, 0); +} +void smb2_oplock_create(struct smb2_create *io, const char *name, uint8_t oplock) +{ + smb2_oplock_create_share(io, name, smb2_util_share_access("RWD"), + oplock); +} + +/* + a wrapper around smblsa_sid_check_privilege, that tries to take + account of the fact that the lsa privileges calls don't expand + group memberships, using an explicit check for administrator. There + must be a better way ... + */ +NTSTATUS torture_smb2_check_privilege(struct smb2_tree *tree, + const char *sid_str, + const char *privilege) +{ + struct dom_sid *sid = NULL; + TALLOC_CTX *tmp_ctx = NULL; + uint32_t rid; + NTSTATUS status; + + tmp_ctx = talloc_new(tree); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sid = dom_sid_parse_talloc(tmp_ctx, sid_str); + if (sid == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_SID; + } + + status = dom_sid_split_rid(tmp_ctx, sid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + if (rid == DOMAIN_RID_ADMINISTRATOR) { + /* assume the administrator has them all */ + TALLOC_FREE(tmp_ctx); + return NT_STATUS_OK; + } + + talloc_free(tmp_ctx); + + return smb2lsa_sid_check_privilege(tree, sid_str, privilege); +} |