From: Jeremy Allison Date: Tue, 30 Aug 2022 22:26:12 +0000 (-0700) Subject: s3: torture: Add a comprehensive SMB1 DFS path torture tester. X-Git-Tag: talloc-2.4.0~1212 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a8ed244148a98e3b1f49ea7de426e795b6a28e06;p=thirdparty%2Fsamba.git s3: torture: Add a comprehensive SMB1 DFS path torture tester. smbtorture3 test is: SMB1-DFS-PATHS Tests open, and then all 4 methods of renaming/hardlinking files: 1). SMBmv 2). SMBtrans2 SETPATHINFO 3). SMBtrans2 SETFILEINFO 4). SMBntrename Also added a test for SMB1findfirst. smbtorture3 test is: SMB1-DFS-SEARCH-PATHS. What this shows is that Windows strips off the SMB1findfirst mask *before* calling the DFS path parser (smbd currently does not). Added so we know how to fix the server code to match Windows behavior in parsing DFS paths in different calls going forward. Passes fully against Windows. Adds knownfails for smbd. Signed-off-by: Jeremy Allison Reviewed-by: Noel Power --- diff --git a/selftest/knownfail.d/dfs_paths b/selftest/knownfail.d/dfs_paths index a65e114eade..ed86a438736 100644 --- a/selftest/knownfail.d/dfs_paths +++ b/selftest/knownfail.d/dfs_paths @@ -1 +1,3 @@ ^samba3.smbtorture_s3.smb2.SMB2-DFS-PATHS.smbtorture\(fileserver\) +^samba3.smbtorture_s3.smb1.SMB1-DFS-PATHS.smbtorture\(fileserver\) +^samba3.smbtorture_s3.smb1.SMB1-DFS-SEARCH-PATHS.smbtorture\(fileserver\) diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py index 82f24dbdfab..3dc3105c057 100755 --- a/source3/selftest/tests.py +++ b/source3/selftest/tests.py @@ -246,6 +246,38 @@ plantestsuite("samba3.smbtorture_s3.smb2.SMB2-DFS-PATHS", smbtorture3, "-mSMB2"]) +# +# SMB1-DFS-PATHS needs to run against a special share msdfs-pathname-share +# This is an empty DFS share with no links, used merely to test +# incoming DFS pathnames and how they map to local paths. +# +plantestsuite("samba3.smbtorture_s3.smb1.SMB1-DFS-PATHS", + "fileserver", + [os.path.join(samba3srcdir, + "script/tests/test_smbtorture_s3.sh"), + 'SMB1-DFS-PATHS', + '//$SERVER_IP/msdfs-pathname-share', + '$USERNAME', + '$PASSWORD', + smbtorture3, + "-mNT1"]) + +# +# SMB1-DFS-SEARCH-PATHS needs to run against a special share msdfs-pathname-share +# This is an empty DFS share with no links, used merely to test +# incoming DFS pathnames and how they map to local paths. +# +plantestsuite("samba3.smbtorture_s3.smb1.SMB1-DFS-SEARCH-PATHS", + "fileserver", + [os.path.join(samba3srcdir, + "script/tests/test_smbtorture_s3.sh"), + 'SMB1-DFS-SEARCH-PATHS', + '//$SERVER_IP/msdfs-pathname-share', + '$USERNAME', + '$PASSWORD', + smbtorture3, + "-mNT1"]) + # # SMB2-STREAM-ACL needs to run against a special share - vfs_wo_fruit # diff --git a/source3/torture/proto.h b/source3/torture/proto.h index bb47c7a43a0..873543d748c 100644 --- a/source3/torture/proto.h +++ b/source3/torture/proto.h @@ -121,6 +121,8 @@ bool run_smb2_sacl(int dummy); bool run_smb2_quota1(int dummy); bool run_smb2_stream_acl(int dummy); bool run_smb2_dfs_paths(int dummy); +bool run_smb1_dfs_paths(int dummy); +bool run_smb1_dfs_search_paths(int dummy); bool run_list_dir_async_test(int dummy); bool run_delete_on_close_non_empty(int dummy); bool run_delete_on_close_nonwrite_delete_yes_test(int dummy); diff --git a/source3/torture/test_smb1_dfs.c b/source3/torture/test_smb1_dfs.c new file mode 100644 index 00000000000..136acde5b9d --- /dev/null +++ b/source3/torture/test_smb1_dfs.c @@ -0,0 +1,2339 @@ +/* + Unix SMB/CIFS implementation. + SMB1 DFS tests. + Copyright (C) Jeremy Allison 2022. + + 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 . +*/ + +#include "includes.h" +#include "torture/proto.h" +#include "client.h" +#include "trans2.h" +#include "../libcli/smb/smbXcli_base.h" +#include "libcli/security/security.h" +#include "libsmb/proto.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth_generic.h" +#include "../librpc/ndr/libndr.h" +#include "libsmb/clirap.h" +#include "async_smb.h" +#include "../lib/util/tevent_ntstatus.h" + +extern fstring host, workgroup, share, password, username, myname; +extern struct cli_credentials *torture_creds; + +/* + * Open an SMB1 file readonly and return the inode number. + */ +static NTSTATUS get_smb1_inode(struct cli_state *cli, + const char *pathname, + uint64_t *ino_ret) +{ + NTSTATUS status; + uint16_t fnum = 0; + SMB_INO_T ino = 0; + + /* + * Open the file. + */ + + status = smb1cli_ntcreatex(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + pathname, + OPLOCK_NONE, /* CreatFlags */ + 0, /* RootDirectoryFid */ + SEC_STD_SYNCHRONIZE| + SEC_FILE_READ_DATA| + SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */ + 0, /* AllocationSize */ + FILE_ATTRIBUTE_NORMAL, /* FileAttributes */ + FILE_SHARE_READ| + FILE_SHARE_WRITE| + FILE_SHARE_DELETE, /* ShareAccess */ + FILE_OPEN, /* CreateDisposition */ + 0, /* CreateOptions */ + 2, /* ImpersonationLevel */ + 0, /* SecurityFlags */ + &fnum); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Get the inode. Note - we can use + * a higher-level cli_XXX function here + * for SMB1 as cli_qfileinfo_basic() + * doesn't use any pathnames, only fnums + * so it isn't affected by DFS pathnames. + */ + status = cli_qfileinfo_basic(cli, + fnum, + NULL, /* attr */ + NULL, /* size */ + NULL, /* create_time */ + NULL, /* access_time */ + NULL, /* write_time */ + NULL, /* change_time */ + &ino); + if (NT_STATUS_IS_OK(status)) { + *ino_ret = (uint64_t)ino; + } + + (void)smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ + return status; +} + +/* + * Check an inode matches a given SMB1 path. + */ +static bool smb1_inode_matches(struct cli_state *cli, + const char *match_pathname, + uint64_t ino_tomatch, + const char *test_pathname) +{ + uint64_t test_ino = 0; + NTSTATUS status; + + status = get_smb1_inode(cli, + test_pathname, + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s: Failed to get ino " + "number for %s, (%s)\n", + __func__, + test_pathname, + nt_errstr(status)); + return false; + } + if (test_ino != ino_tomatch) { + printf("%s: Inode missmatch, ino_tomatch (%s) " + "ino=%"PRIu64" test (%s) " + "ino=%"PRIu64"\n", + __func__, + match_pathname, + ino_tomatch, + test_pathname, + test_ino); + return false; + } + return true; +} + +/* + * Delete an SMB1 file on a DFS share. + */ +static NTSTATUS smb1_dfs_delete(struct cli_state *cli, + const char *pathname) +{ + NTSTATUS status; + uint16_t fnum = 0; + + /* + * Open the file. + */ + + status = smb1cli_ntcreatex(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + pathname, + OPLOCK_NONE, /* CreatFlags */ + 0, /* RootDirectoryFid */ + SEC_STD_SYNCHRONIZE| + SEC_STD_DELETE, /* DesiredAccess */ + 0, /* AllocationSize */ + FILE_ATTRIBUTE_NORMAL, /* FileAttributes */ + FILE_SHARE_READ| + FILE_SHARE_WRITE| + FILE_SHARE_DELETE, /* ShareAccess */ + FILE_OPEN, /* CreateDisposition */ + 0, /* CreateOptions */ + 2, /* ImpersonationLevel */ + 0, /* SecurityFlags */ + &fnum); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Set delete on close. Note - we can use + * a higher-level cli_XXX function here + * for SMB1 as cli_nt_delete_on_close() + * doesn't use any pathnames, only fnums + * so it isn't affected by DFS pathnames. + */ + /* + */ + status = cli_nt_delete_on_close(cli, fnum, 1); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ +} + +static void smb1_mv_done(struct tevent_req *subreq); + +struct smb1_mv_state { + uint16_t vwv[1]; +}; + +static struct tevent_req *smb1_mv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_state *cli, + const char *src_dfs_name, + const char *target_name) +{ + uint8_t *bytes = NULL; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct smb1_mv_state *state = NULL; + + req = tevent_req_create(mem_ctx, + &state, + struct smb1_mv_state); + if (req == NULL) { + return NULL; + } + + PUSH_LE_U16(state->vwv, + 0, + FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_DIRECTORY); + + bytes = talloc_array(state, uint8_t, 1); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + bytes[0] = 4; + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(cli->conn), + src_dfs_name, + strlen(src_dfs_name)+1, + NULL); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + bytes = talloc_realloc(state, + bytes, + uint8_t, + talloc_get_size(bytes)+1); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + bytes[talloc_get_size(bytes)-1] = 4; + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(cli->conn), + target_name, + strlen(target_name)+1, + NULL); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + subreq = cli_smb_send(state, + ev, + cli, + SMBmv, + 0, /* additional_flags */ + 0, /* additional_flags2 */ + 1, + state->vwv, + talloc_get_size(bytes), + bytes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1_mv_done, req); + return req; +} + +static void smb1_mv_done(struct tevent_req *subreq) +{ + NTSTATUS status = cli_smb_recv(subreq, + NULL, + NULL, + 0, + NULL, + NULL, + NULL, + NULL); + tevent_req_simple_finish_ntstatus(subreq, + status); +} + +static NTSTATUS smb1_mv_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/* + * Rename an SMB1 file on a DFS share. SMBmv version. + */ +static NTSTATUS smb1_mv(struct cli_state *cli, + const char *src_dfs_name, + const char *target_name) +{ + TALLOC_CTX *frame = NULL; + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status; + + frame = talloc_stackframe(); + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + req = smb1_mv_send(frame, + ev, + cli, + src_dfs_name, + target_name); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + + status = smb1_mv_recv(req); + + fail: + + TALLOC_FREE(frame); + return status; +} + +static bool test_smb1_mv(struct cli_state *cli, + const char *src_dfs_name) +{ + uint64_t test_ino = 0; + NTSTATUS status; + + status = smb1_mv(cli, + src_dfs_name, + "BAD\\BAD\\renamed_file"); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d SMBmv of %s -> %s should succeed " + "got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\renamed_file", + nt_errstr(status)); + return false; + } + + /* Ensure we did rename. */ + status = get_smb1_inode(cli, + "BAD\\BAD\\renamed_file", + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino " + "number for %s, (%s)\n", + __FILE__, + __LINE__, + "BAD\\BAD\\renamed_file", + nt_errstr(status)); + return false; + } + + /* Put it back. */ + status = smb1_mv(cli, + "BAD\\BAD\\renamed_file", + src_dfs_name); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d SMBmv of %s -> %s should succeed " + "got %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\renamed_file", + src_dfs_name, + nt_errstr(status)); + return false; + } + + /* Ensure we did put it back. */ + status = get_smb1_inode(cli, + src_dfs_name, + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino " + "number for %s, (%s)\n", + __FILE__, + __LINE__, + src_dfs_name, + nt_errstr(status)); + return false; + } + + /* Try with a non-DFS name. */ + status = smb1_mv(cli, + src_dfs_name, + "renamed_file"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) { + /* Fails I think as target becomes "" on server. */ + printf("%s:%d SMBmv of %s -> %s should get " + "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "renamed_file", + nt_errstr(status)); + return false; + } + + /* Try with a non-DFS name. */ + status = smb1_mv(cli, + src_dfs_name, + "BAD\\renamed_file"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) { + /* Fails I think as target becomes "" on server. */ + printf("%s:%d SMBmv of %s -> %s should get " + "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\renamed_file", + nt_errstr(status)); + return false; + } + return true; +} + +static void smb1_setpathinfo_done(struct tevent_req *subreq); + +struct smb1_setpathinfo_state { + uint16_t setup; + uint8_t *param; + uint8_t *data; +}; + +static struct tevent_req *smb1_setpathinfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_state *cli, + const char *src_dfs_name, + const char *target_name, + uint16_t info_level) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct smb1_setpathinfo_state *state = NULL; + smb_ucs2_t *converted_str = NULL; + size_t converted_size_bytes = 0; + bool ok = false; + + req = tevent_req_create(mem_ctx, + &state, + struct smb1_setpathinfo_state); + if (req == NULL) { + return NULL; + } + + PUSH_LE_U16(&state->setup, 0, TRANSACT2_SETPATHINFO); + + state->param = talloc_zero_array(state, uint8_t, 6); + if (tevent_req_nomem(state->param, req)) { + return tevent_req_post(req, ev); + } + PUSH_LE_U16(state->param, 0, info_level); + + state->param = trans2_bytes_push_str(state->param, + smbXcli_conn_use_unicode(cli->conn), + src_dfs_name, + strlen(src_dfs_name)+1, + NULL); + if (tevent_req_nomem(state->param, req)) { + return tevent_req_post(req, ev); + } + + ok = push_ucs2_talloc(state, + &converted_str, + target_name, + &converted_size_bytes); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + /* + * W2K8 insists the dest name is not null + * terminated. Remove the last 2 zero bytes + * and reduce the name length. + */ + + if (converted_size_bytes < 2) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + converted_size_bytes -= 2; + + state->data = talloc_zero_array(state, + uint8_t, + 12 + converted_size_bytes); + if (tevent_req_nomem(state->data, req)) { + return tevent_req_post(req, ev); + } + + SIVAL(state->data, 8, converted_size_bytes); + memcpy(state->data + 12, converted_str, converted_size_bytes); + + subreq = cli_trans_send(state, /* mem ctx. */ + ev,/* event ctx. */ + cli,/* cli_state. */ + 0,/* additional_flags2 */ + SMBtrans2, /* cmd. */ + NULL,/* pipe name. */ + -1,/* fid. */ + 0,/* function. */ + 0,/* flags. */ + &state->setup,/* setup. */ + 1,/* num setup uint16_t words. */ + 0,/* max returned setup. */ + state->param,/* param. */ + talloc_get_size(state->param),/* num param. */ + 2,/* max returned param. */ + state->data,/* data. */ + talloc_get_size(state->data),/* num data. */ + 0);/* max returned data. */ + + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1_setpathinfo_done, req); + return req; +} + +static void smb1_setpathinfo_done(struct tevent_req *subreq) +{ + NTSTATUS status = cli_trans_recv(subreq, + NULL, + NULL, + NULL, + 0, + NULL, + NULL, + 0, + NULL, + NULL, + 0, + NULL); + tevent_req_simple_finish_ntstatus(subreq, + status); +} + +static NTSTATUS smb1_setpathinfo_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/* + * Rename or hardlink an SMB1 file on a DFS share. SMB1 setpathinfo + * (pathnames only) version. + */ +static NTSTATUS smb1_setpathinfo(struct cli_state *cli, + const char *src_dfs_name, + const char *target_name, + uint16_t info_level) +{ + TALLOC_CTX *frame = NULL; + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status; + + frame = talloc_stackframe(); + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + req = smb1_setpathinfo_send(frame, + ev, + cli, + src_dfs_name, + target_name, + info_level); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + + status = smb1_setpathinfo_recv(req); + + fail: + + TALLOC_FREE(frame); + return status; +} + +static NTSTATUS smb1_setpathinfo_rename(struct cli_state *cli, + const char *src_dfs_name, + const char *target_name) +{ + return smb1_setpathinfo(cli, + src_dfs_name, + target_name, + SMB_FILE_RENAME_INFORMATION); +} + +static bool test_smb1_setpathinfo_rename(struct cli_state *cli, + const char *src_dfs_name) +{ + uint64_t test_ino = 0; + NTSTATUS status; + const char *putback_path = NULL; + + /* + * On Windows, setpathinfo rename where the target contains + * any directory separator returns STATUS_NOT_SUPPORTED. + * + * MS-SMB behavior note: <133> Section 3.3.5.10.6: + * + * "If the file name pointed to by the FileName parameter of the + * FILE_RENAME_INFORMATION structure contains a separator character, + * then the request fails with STATUS_NOT_SUPPORTED." + */ + status = smb1_setpathinfo_rename(cli, + src_dfs_name, + "BAD\\BAD\\renamed_file"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + printf("%s:%d SMB1 setpathinfo rename of %s -> %s should get " + "NT_STATUS_NOT_SUPPORTED got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\renamed_file", + nt_errstr(status)); + return false; + } + + /* Try with a non-DFS name. */ + status = smb1_setpathinfo_rename(cli, + src_dfs_name, + "renamed_file"); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d SMB1 setpathinfo rename of %s -> %s " + "should succeed got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "renamed_file", + nt_errstr(status)); + return false; + } + + /* Ensure we did rename. */ + status = get_smb1_inode(cli, + "BAD\\BAD\\renamed_file", + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino " + "number for %s, (%s)\n", + __FILE__, + __LINE__, + "BAD\\BAD\\renamed_file", + nt_errstr(status)); + return false; + } + + /* + * To put it back we need to reverse the DFS-ness of src + * and destination paths. + */ + putback_path = strrchr(src_dfs_name, '\\'); + if (putback_path == NULL) { + printf("%s:%d non DFS path %s passed. Internal error\n", + __FILE__, + __LINE__, + src_dfs_name); + return false; + } + /* Walk past the last '\\' */ + putback_path++; + + /* Put it back. */ + status = smb1_setpathinfo_rename(cli, + "BAD\\BAD\\renamed_file", + putback_path); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d SMB1 setpathinfo rename of %s -> %s " + "should succeed got %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\renamed_file", + putback_path, + nt_errstr(status)); + return false; + } + + /* Ensure we did rename. */ + status = get_smb1_inode(cli, + src_dfs_name, + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino " + "number for %s, (%s)\n", + __FILE__, + __LINE__, + src_dfs_name, + nt_errstr(status)); + return false; + } + + return true; +} + +static NTSTATUS smb1_setpathinfo_hardlink(struct cli_state *cli, + const char *src_dfs_name, + const char *target_name) +{ + return smb1_setpathinfo(cli, + src_dfs_name, + target_name, + SMB_FILE_LINK_INFORMATION); +} + +static bool test_smb1_setpathinfo_hardlink(struct cli_state *cli, + const char *src_dfs_name) +{ + NTSTATUS status; + + /* + * On Windows, setpathinfo rename where the target contains + * any directory separator returns STATUS_NOT_SUPPORTED. + * + * MS-SMB behavior note: <133> Section 3.3.5.10.6: + * + * "If the file name pointed to by the FileName parameter of the + * FILE_RENAME_INFORMATION structure contains a separator character, + * then the request fails with STATUS_NOT_SUPPORTED." + * + * setpathinfo info level SMB_FILE_LINK_INFORMATION + * seems to do the same, but this could be an artifact + * of the Windows version tested (Win2K8). I will + * revisit this when I'm able to test against + * a later Windows version with a DFS server. + */ + status = smb1_setpathinfo_hardlink(cli, + src_dfs_name, + "BAD\\BAD\\hlink"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + printf("%s:%d SMB1 setpathinfo hardlink of %s -> %s should get " + "NT_STATUS_NOT_SUPPORTED got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\hlink", + nt_errstr(status)); + return false; + } + + /* Try with a non-DFS name. */ + /* + * At least on Windows 2008 this also fails with + * NT_STATUS_NOT_SUPPORTED, leading me to believe + * setting hardlinks is only supported via NTrename + * in SMB1. + */ + status = smb1_setpathinfo_hardlink(cli, + src_dfs_name, + "hlink"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + printf("%s:%d SMB1 setpathinfo hardlink of %s -> %s should get " + "NT_STATUS_NOT_SUPPORTED got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "hlink", + nt_errstr(status)); + return false; + } + return true; +} + +static void smb1_ntrename_done(struct tevent_req *subreq); + +struct smb1_ntrename_state { + uint16_t vwv[4]; +}; + +static struct tevent_req *smb1_ntrename_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_state *cli, + const char *src_dfs_name, + const char *target_name, + uint16_t rename_flag) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct smb1_ntrename_state *state = NULL; + uint8_t *bytes = NULL; + + req = tevent_req_create(mem_ctx, + &state, + struct smb1_ntrename_state); + if (req == NULL) { + return NULL; + } + + PUSH_LE_U16(state->vwv, + 0, + FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_DIRECTORY); + PUSH_LE_U16(state->vwv, 2, rename_flag); + + bytes = talloc_array(state, uint8_t, 1); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + bytes[0] = 4; + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(cli->conn), + src_dfs_name, + strlen(src_dfs_name)+1, + NULL); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + bytes = talloc_realloc(state, + bytes, + uint8_t, + talloc_get_size(bytes)+1); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + bytes[talloc_get_size(bytes)-1] = 4; + bytes = smb_bytes_push_str(bytes, + smbXcli_conn_use_unicode(cli->conn), + target_name, + strlen(target_name)+1, + NULL); + if (tevent_req_nomem(bytes, req)) { + return tevent_req_post(req, ev); + } + + subreq = cli_smb_send(state, + ev, + cli, + SMBntrename, + 0, /* additional_flags */ + 0, /* additional_flags2 */ + 4, + state->vwv, + talloc_get_size(bytes), + bytes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1_ntrename_done, req); + return req; +} + +static void smb1_ntrename_done(struct tevent_req *subreq) +{ + NTSTATUS status = cli_smb_recv(subreq, + NULL, + NULL, + 0, + NULL, + NULL, + NULL, + NULL); + tevent_req_simple_finish_ntstatus(subreq, status); +} + +static NTSTATUS smb1_ntrename_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/* + * Rename or hardlink an SMB1 file on a DFS share. SMB1 ntrename version. + * (pathnames only). + */ +static NTSTATUS smb1_ntrename(struct cli_state *cli, + const char *src_dfs_name, + const char *target_name, + uint16_t rename_flag) +{ + TALLOC_CTX *frame = NULL; + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status; + + frame = talloc_stackframe(); + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + req = smb1_ntrename_send(frame, + ev, + cli, + src_dfs_name, + target_name, + rename_flag); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + + status = smb1_ntrename_recv(req); + + fail: + + TALLOC_FREE(frame); + return status; +} +/* + * Rename an SMB1 file on a DFS share. SMB1 ntrename version. + */ +static NTSTATUS smb1_ntrename_rename(struct cli_state *cli, + const char *src_dfs_name, + const char *target_name) +{ + return smb1_ntrename(cli, + src_dfs_name, + target_name, + RENAME_FLAG_RENAME); +} + + +static bool test_smb1_ntrename_rename(struct cli_state *cli, + const char *src_dfs_name) +{ + uint64_t test_ino = 0; + NTSTATUS status; + + /* Try with a non-DFS name. */ + status = smb1_ntrename_rename(cli, + src_dfs_name, + "renamed_file"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) { + /* Fails I think as target becomes "" on server. */ + printf("%s:%d SMB1 ntrename rename of %s -> %s should get " + "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "renamed_file", + nt_errstr(status)); + return false; + } + + status = smb1_ntrename_rename(cli, + src_dfs_name, + "BAD\\BAD\\renamed_file"); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d SMB1 ntrename rename of %s -> %s should " + "succeed got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\renamed_file", + nt_errstr(status)); + return false; + } + + /* Ensure we did rename. */ + status = get_smb1_inode(cli, + "BAD\\BAD\\renamed_file", + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino " + "number for %s, (%s)\n", + __FILE__, + __LINE__, + "BAD\\BAD\\renamed_file", + nt_errstr(status)); + return false; + } + + /* Put it back. */ + status = smb1_ntrename_rename(cli, + "BAD\\BAD\\renamed_file", + src_dfs_name); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d SMB1 ntrename rename of %s -> %s " + "should succeed got %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\renamed_file", + src_dfs_name, + nt_errstr(status)); + return false; + } + + /* Ensure we did rename. */ + status = get_smb1_inode(cli, + src_dfs_name, + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino " + "number for %s, (%s)\n", + __FILE__, + __LINE__, + src_dfs_name, + nt_errstr(status)); + return false; + } + + return true; +} + +/* + * Hard link an SMB1 file on a DFS share. SMB1 ntrename version. + */ +static NTSTATUS smb1_ntrename_hardlink(struct cli_state *cli, + const char *src_dfs_name, + const char *target_name) +{ + return smb1_ntrename(cli, + src_dfs_name, + target_name, + RENAME_FLAG_HARD_LINK); +} + +static bool test_smb1_ntrename_hardlink(struct cli_state *cli, + const char *src_dfs_name) +{ + uint64_t test_ino = 0; + NTSTATUS status; + bool retval = false; + + /* Try with a non-DFS name. */ + status = smb1_ntrename_hardlink(cli, + src_dfs_name, + "hlink"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) { + /* Fails I think as target becomes "" on server. */ + printf("%s:%d SMB1 ntrename of %s -> %s should get " + "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "hlink", + nt_errstr(status)); + return false; + } + + status = smb1_ntrename_hardlink(cli, + src_dfs_name, + "BAD\\BAD\\hlink"); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d SMB1 ntrename hardlink of %s -> %s " + "should succeed got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\hlink", + nt_errstr(status)); + goto out; + } + + /* Ensure we did hardlink. */ + status = get_smb1_inode(cli, + "BAD\\BAD\\hlink", + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino " + "number for %s, (%s)\n", + __FILE__, + __LINE__, + "BAD\\BAD\\hlink", + nt_errstr(status)); + goto out; + } + + retval = smb1_inode_matches(cli, + "BAD\\BAD\\hlink", + test_ino, + src_dfs_name); + if (!retval) { + printf("%s:%d smb1_inode_matches failed for " + "%s %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\hlink"); + goto out; + } + + out: + + /* Remove the hardlink to clean up. */ + (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink"); + return retval; +} + +static void smb1_setfileinfo_done(struct tevent_req *subreq); + +struct smb1_setfileinfo_state { + uint16_t setup; + uint8_t param[6]; + uint8_t *data; +}; + +static struct tevent_req *smb1_setfileinfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_state *cli, + uint16_t fnum, + const char *target_name, + uint16_t info_level) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct smb1_setfileinfo_state *state = NULL; + smb_ucs2_t *converted_str = NULL; + size_t converted_size_bytes = 0; + bool ok = false; + + req = tevent_req_create(mem_ctx, + &state, + struct smb1_setfileinfo_state); + if (req == NULL) { + return NULL; + } + + PUSH_LE_U16(&state->setup, 0, TRANSACT2_SETPATHINFO); + + PUSH_LE_U16(state->param, 0, fnum); + PUSH_LE_U16(state->param, 2, info_level); + + ok = push_ucs2_talloc(state, + &converted_str, + target_name, + &converted_size_bytes); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + /* + * W2K8 insists the dest name is not null + * terminated. Remove the last 2 zero bytes + * and reduce the name length. + */ + + if (converted_size_bytes < 2) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + converted_size_bytes -= 2; + + state->data = talloc_zero_array(state, + uint8_t, + 12 + converted_size_bytes); + if (tevent_req_nomem(state->data, req)) { + return tevent_req_post(req, ev); + } + + SIVAL(state->data, 8, converted_size_bytes); + memcpy(state->data + 12, converted_str, converted_size_bytes); + + subreq = cli_trans_send(state, /* mem ctx. */ + ev,/* event ctx. */ + cli,/* cli_state. */ + 0,/* additional_flags2 */ + SMBtrans2, /* cmd. */ + NULL,/* pipe name. */ + -1,/* fid. */ + 0,/* function. */ + 0,/* flags. */ + &state->setup,/* setup. */ + 1,/* num setup uint16_t words. */ + 0,/* max returned setup. */ + state->param,/* param. */ + 6,/* num param. */ + 2,/* max returned param. */ + state->data,/* data. */ + talloc_get_size(state->data),/* num data. */ + 0);/* max returned data. */ + + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smb1_setfileinfo_done, req); + return req; +} + +static void smb1_setfileinfo_done(struct tevent_req *subreq) +{ + NTSTATUS status = cli_trans_recv(subreq, + NULL, + NULL, + NULL, + 0, + NULL, + NULL, + 0, + NULL, + NULL, + 0, + NULL); + tevent_req_simple_finish_ntstatus(subreq, + status); +} + +static NTSTATUS smb1_setfileinfo_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/* + * Rename or hardlink an SMB1 file on a DFS share. + * setfileinfo (file handle + target pathname) version. + */ +static NTSTATUS smb1_setfileinfo(struct cli_state *cli, + uint16_t fnum, + const char *target_name, + uint16_t info_level) +{ + TALLOC_CTX *frame = NULL; + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status; + + frame = talloc_stackframe(); + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + req = smb1_setfileinfo_send(frame, + ev, + cli, + fnum, + target_name, + info_level); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + + status = smb1_setfileinfo_recv(req); + + fail: + + TALLOC_FREE(frame); + return status; +} + +static NTSTATUS smb1_setfileinfo_rename(struct cli_state *cli, + uint16_t fnum, + const char *target_name) +{ + return smb1_setfileinfo(cli, + fnum, + target_name, + SMB_FILE_RENAME_INFORMATION); +} + +/* + * On Windows, rename using a file handle as source + * is not supported. + */ + +static bool test_smb1_setfileinfo_rename(struct cli_state *cli, + const char *src_dfs_name) +{ + uint16_t fnum = (uint16_t)-1; + NTSTATUS status; + bool retval = false; + + /* First open the source file. */ + status = smb1cli_ntcreatex(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + src_dfs_name, + OPLOCK_NONE, /* CreatFlags */ + 0, /* RootDirectoryFid */ + SEC_STD_SYNCHRONIZE| + SEC_STD_DELETE, /* DesiredAccess */ + 0, /* AllocationSize */ + FILE_ATTRIBUTE_NORMAL, /* FileAttributes */ + FILE_SHARE_READ| + FILE_SHARE_WRITE| + FILE_SHARE_DELETE, /* ShareAccess */ + FILE_OPEN, /* CreateDisposition */ + 0, /* CreateOptions */ + 2, /* ImpersonationLevel */ + 0, /* SecurityFlags */ + &fnum); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d failed to open %s, %s\n", + __FILE__, + __LINE__, + src_dfs_name, + nt_errstr(status)); + goto out; + } + + /* + * On Windows rename given a file handle returns + * NT_STATUS_UNSUCCESSFUL (not documented in MS-SMB). + */ + + status = smb1_setfileinfo_rename(cli, + fnum, + "BAD\\BAD\\renamed_file"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) { + printf("%s:%d SMB1 setfileinfo rename of %s -> %s should get " + "NT_STATUS_UNSUCCESSFUL got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\hlink", + nt_errstr(status)); + goto out; + } + + /* Try with a non-DFS name - still gets NT_STATUS_UNSUCCESSFUL. */ + status = smb1_setfileinfo_rename(cli, + fnum, + "renamed_file"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) { + printf("%s:%d SMB1 setfileinfo rename of %s -> %s should get " + "NT_STATUS_UNSUCCESSFUL got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "hlink", + nt_errstr(status)); + goto out; + } + + retval = true; + + out: + + if (fnum != (uint16_t)-1) { + (void)smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ + } + + (void)smb1_dfs_delete(cli, "BAD\\BAD\\renamed_file"); + return retval; +} + + +static NTSTATUS smb1_setfileinfo_hardlink(struct cli_state *cli, + uint16_t fnum, + const char *target_name) +{ + return smb1_setfileinfo(cli, + fnum, + target_name, + SMB_FILE_LINK_INFORMATION); +} + +/* + * On Windows, hardlink using a file handle as source + * is not supported. + */ + +static bool test_smb1_setfileinfo_hardlink(struct cli_state *cli, + const char *src_dfs_name) +{ + uint16_t fnum = (uint16_t)-1; + NTSTATUS status; + bool retval = false; + + /* First open the source file. */ + status = smb1cli_ntcreatex(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + src_dfs_name, + OPLOCK_NONE, /* CreatFlags */ + 0, /* RootDirectoryFid */ + SEC_STD_SYNCHRONIZE| + SEC_RIGHTS_FILE_READ, /* DesiredAccess */ + 0, /* AllocationSize */ + FILE_ATTRIBUTE_NORMAL, /* FileAttributes */ + FILE_SHARE_READ| + FILE_SHARE_WRITE| + FILE_SHARE_DELETE, /* ShareAccess */ + FILE_OPEN, /* CreateDisposition */ + 0, /* CreateOptions */ + 2, /* ImpersonationLevel */ + 0, /* SecurityFlags */ + &fnum); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d failed to open %s, %s\n", + __FILE__, + __LINE__, + src_dfs_name, + nt_errstr(status)); + goto out; + } + + /* + * On Windows hardlink given a file handle returns + * NT_STATUS_UNSUCCESSFUL (not documented in MS-SMB). + */ + + status = smb1_setfileinfo_hardlink(cli, + fnum, + "BAD\\BAD\\hlink"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) { + printf("%s:%d SMB1 setfileinfo hardlink of %s -> %s should get " + "NT_STATUS_UNSUCCESSFUL got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "BAD\\BAD\\hlink", + nt_errstr(status)); + goto out; + } + + /* Try with a non-DFS name - still gets NT_STATUS_UNSUCCESSFUL. */ + status = smb1_setfileinfo_hardlink(cli, + fnum, + "hlink"); + if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) { + printf("%s:%d SMB1 setfileinfo hardlink of %s -> %s should get " + "NT_STATUS_UNSUCCESSFUL got %s\n", + __FILE__, + __LINE__, + src_dfs_name, + "hlink", + nt_errstr(status)); + goto out; + } + + retval = true; + + out: + + if (fnum != (uint16_t)-1) { + (void)smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ + } + + (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink"); + return retval; +} + +/* + * According to: + + * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/dc9978d7-6299-4c5a-a22d-a039cdc716ea + * + * (Characters " \ / [ ] : | < > + = ; , * ?, + * and control characters in range 0x00 through + * 0x1F, inclusive, are illegal in a share name) + * + * But Windows server only checks in DFS sharenames ':'. All other + * share names are allowed. + */ + +static bool test_smb1_dfs_sharenames(struct cli_state *cli, + const char *dfs_root_share_name, + uint64_t root_ino) +{ + char test_path[20]; + const char *test_str = "/[]:|<>+=;,*?"; + const char *p; + unsigned int i; + bool ino_matched = false; + + /* Setup template pathname. */ + memcpy(test_path, "\\SERVER\\X", 10); + + /* Test invalid control characters. */ + for (i = 1; i < 0x20; i++) { + test_path[8] = i; + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + test_path); + if (!ino_matched) { + return false; + } + } + + /* Test explicit invalid characters. */ + for (p = test_str; *p != '\0'; p++) { + test_path[8] = *p; + if (*p == ':') { + /* + * Only ':' is treated as an INVALID sharename + * for a DFS SERVER\\SHARE path. + */ + uint64_t test_ino = 0; + NTSTATUS status = get_smb1_inode(cli, + test_path, + &test_ino); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) { + printf("%s:%d Open of %s should get " + "NT_STATUS_OBJECT_NAME_INVALID, got %s\n", + __FILE__, + __LINE__, + test_path, + nt_errstr(status)); + return false; + } + } else { + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + test_path); + if (!ino_matched) { + return false; + } + } + } + return true; +} + +/* + * "Raw" test of SMB1 paths to a DFS share. + * We must (mostly) use the lower level smb1cli_XXXX() interfaces, + * not the cli_XXX() ones here as the ultimate goal is to fix our + * cli_XXX() interfaces to work transparently over DFS. + * + * So here, we're testing the server code, not the client code. + * + * Passes cleanly against Windows. + */ + +bool run_smb1_dfs_paths(int dummy) +{ + struct cli_state *cli = NULL; + NTSTATUS status; + bool dfs_supported = false; + char *dfs_root_share_name = NULL; + uint64_t root_ino = 0; + uint64_t test_ino = 0; + bool ino_matched = false; + bool retval = false; + bool ok = false; + unsigned int i; + uint16_t fnum = (uint16_t)-1; + + printf("Starting SMB1-DFS-PATHS\n"); + + if (!torture_init_connection(&cli)) { + return false; + } + + if (!torture_open_connection(&cli, 0)) { + return false; + } + + /* Ensure this is a DFS share. */ + dfs_supported = smbXcli_conn_dfs_supported(cli->conn); + if (!dfs_supported) { + printf("Server %s does not support DFS\n", + smbXcli_conn_remote_name(cli->conn)); + return false; + } + dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb1.tcon); + if (!dfs_supported) { + printf("Share %s does not support DFS\n", + cli->share); + return false; + } + + /* Start with an empty share. */ + (void)smb1_dfs_delete(cli, "BAD\\BAD\\BAD"); + (void)smb1_dfs_delete(cli, "BAD\\BAD\\file"); + (void)smb1_dfs_delete(cli, "BAD\\BAD\\renamed_file"); + (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink"); + + /* + * Create the "official" DFS share root name. + */ + dfs_root_share_name = talloc_asprintf(talloc_tos(), + "\\%s\\%s", + smbXcli_conn_remote_name(cli->conn), + cli->share); + if (dfs_root_share_name == NULL) { + printf("Out of memory\n"); + return false; + } + + /* Get the share root inode number. */ + status = get_smb1_inode(cli, + dfs_root_share_name, + &root_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Failed to get ino number for share root %s, (%s)\n", + __FILE__, + __LINE__, + dfs_root_share_name, + nt_errstr(status)); + return false; + } + + /* + * Test the Windows algorithm for parsing DFS names. + */ + /* + * A single "SERVER" element should open and match the share root. + */ + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + smbXcli_conn_remote_name(cli->conn)); + if (!ino_matched) { + printf("%s:%d Failed to match ino number for %s\n", + __FILE__, + __LINE__, + smbXcli_conn_remote_name(cli->conn)); + return false; + } + + /* An "" (empty) server name should open and match the share root. */ + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + ""); + if (!ino_matched) { + printf("%s:%d Failed to match ino number for %s\n", + __FILE__, + __LINE__, + ""); + return false; + } + + /* + * For SMB1 the server just strips off any number of leading '\\' + * characters. Show this is the case. + */ + for (i = 0; i < 10; i++) { + char leading_backslash_name[20]; + leading_backslash_name[i] = '\\'; + memcpy(&leading_backslash_name[i+1], + "SERVER", + strlen("SERVER")+1); + + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + leading_backslash_name); + if (!ino_matched) { + printf("%s:%d Failed to match ino number for %s\n", + __FILE__, + __LINE__, + leading_backslash_name); + return false; + } + } + + /* A "BAD" server name should open and match the share root. */ + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + "BAD"); + if (!ino_matched) { + printf("%s:%d Failed to match ino number for %s\n", + __FILE__, + __LINE__, + "BAD"); + return false; + } + /* + * A "BAD\\BAD" server and share name should open + * and match the share root. + */ + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + "BAD\\BAD"); + if (!ino_matched) { + printf("%s:%d Failed to match ino number for %s\n", + __FILE__, + __LINE__, + "BAD\\BAD"); + return false; + } + /* + * Trying to open "BAD\\BAD\\BAD" should get + * NT_STATUS_OBJECT_NAME_NOT_FOUND. + */ + status = get_smb1_inode(cli, + "BAD\\BAD\\BAD", + &test_ino); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + printf("%s:%d Open of %s should get " + "STATUS_OBJECT_NAME_NOT_FOUND, got %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\BAD", + nt_errstr(status)); + return false; + } + /* + * Trying to open "BAD\\BAD\\BAD\\BAD" should get + * NT_STATUS_OBJECT_PATH_NOT_FOUND. + */ + status = get_smb1_inode(cli, + "BAD\\BAD\\BAD\\BAD", + &test_ino); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) { + printf("%s:%d Open of %s should get " + "STATUS_OBJECT_NAME_NOT_FOUND, got %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\BAD\\BAD", + nt_errstr(status)); + return false; + } + /* + * Test for invalid pathname characters in the servername. + * They are ignored, and it still opens the share root. + */ + ino_matched = smb1_inode_matches(cli, + dfs_root_share_name, + root_ino, + "::::"); + if (!ino_matched) { + printf("%s:%d Failed to match ino number for %s\n", + __FILE__, + __LINE__, + "::::"); + return false; + } + + /* + * Test for invalid pathname characters in the sharename. + * Invalid sharename characters should still be flagged as + * NT_STATUS_OBJECT_NAME_INVALID. It turns out only ':' + * is considered an invalid sharename character. + */ + ok = test_smb1_dfs_sharenames(cli, + dfs_root_share_name, + root_ino); + if (!ok) { + return false; + } + + status = smb1cli_ntcreatex(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + "BAD\\BAD\\file", + OPLOCK_NONE, /* CreatFlags */ + 0, /* RootDirectoryFid */ + SEC_STD_SYNCHRONIZE| + SEC_STD_DELETE | + SEC_FILE_READ_DATA| + SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */ + 0, /* AllocationSize */ + FILE_ATTRIBUTE_NORMAL, /* FileAttributes */ + FILE_SHARE_READ| + FILE_SHARE_WRITE| + FILE_SHARE_DELETE, /* ShareAccess */ + FILE_CREATE, /* CreateDisposition */ + 0, /* CreateOptions */ + 2, /* ImpersonationLevel */ + 0, /* SecurityFlags */ + &fnum); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d smb1cli_ntcreatex on %s returned %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\file", + nt_errstr(status)); + return false; + } + + /* Close "file" handle. */ + (void)smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ + fnum = (uint16_t)-1; + + /* + * Trying to open "BAD\\BAD\\file" should now get + * a valid inode. + */ + status = get_smb1_inode(cli, + "BAD\\BAD\\file", + &test_ino); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d Open of %s should succeed " + "got %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\file", + nt_errstr(status)); + goto err; + } + + /* + * Test different SMB1 renames + * and hard links. + */ + + /* SMBmv only does rename. */ + ok = test_smb1_mv(cli, + "BAD\\BAD\\file"); + if (!ok) { + goto err; + } + + ok = test_smb1_setpathinfo_rename(cli, + "BAD\\BAD\\file"); + if (!ok) { + goto err; + } + + ok = test_smb1_setpathinfo_hardlink(cli, + "BAD\\BAD\\file"); + if (!ok) { + goto err; + } + + ok = test_smb1_setfileinfo_rename(cli, + "BAD\\BAD\\file"); + if (!ok) { + goto err; + } + + ok = test_smb1_setfileinfo_hardlink(cli, + "BAD\\BAD\\file"); + if (!ok) { + goto err; + } + + ok = test_smb1_ntrename_rename(cli, + "BAD\\BAD\\file"); + if (!ok) { + goto err; + } + + ok = test_smb1_ntrename_hardlink(cli, + "BAD\\BAD\\file"); + if (!ok) { + goto err; + } + + retval = true; + + err: + + if (fnum != (uint16_t)-1) { + (void)smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ + } + + /* Delete anything we made. */ + (void)smb1_dfs_delete(cli, "BAD\\BAD\\BAD"); + (void)smb1_dfs_delete(cli, "BAD\\BAD\\file"); + (void)smb1_dfs_delete(cli, "BAD\\BAD\\renamed_file"); + (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink"); + return retval; +} + +/* + * SMB1 Findfirst. This is a minimal implementation + * that expects all filename returns in one packet. + * We're only using this to test the search DFS pathname + * parsing. + */ + +/**************************************************************************** + Calculate a safe next_entry_offset. +****************************************************************************/ + +static size_t calc_next_entry_offset(const uint8_t *base, + const uint8_t *pdata_end) +{ + size_t next_entry_offset = (size_t)PULL_LE_U32(base,0); + + if (next_entry_offset == 0 || + base + next_entry_offset < base || + base + next_entry_offset > pdata_end) { + next_entry_offset = pdata_end - base; + } + return next_entry_offset; +} + +static size_t get_filename(TALLOC_CTX *ctx, + struct cli_state *cli, + const uint8_t *base_ptr, + uint16_t recv_flags2, + const uint8_t *p, + const uint8_t *pdata_end, + struct file_info *finfo) +{ + size_t ret = 0; + const uint8_t *base = p; + size_t namelen = 0; + size_t slen = 0; + + ZERO_STRUCTP(finfo); + + if (pdata_end - base < 94) { + return pdata_end - base; + } + p += 4; /* next entry offset */ + p += 4; /* fileindex */ + /* Offset zero is "create time", not "change time". */ + p += 8; + finfo->atime_ts = interpret_long_date((const char *)p); + p += 8; + finfo->mtime_ts = interpret_long_date((const char *)p); + p += 8; + finfo->ctime_ts = interpret_long_date((const char *)p); + p += 8; + finfo->size = PULL_LE_U64(p, 0); + p += 8; + p += 8; /* alloc size */ + finfo->attr = PULL_LE_U32(p, 0); + p += 4; + namelen = PULL_LE_U32(p, 0); + p += 4; + p += 4; /* EA size */ + slen = PULL_LE_U8(p, 0); + if (slen > 24) { + /* Bad short name length. */ + return pdata_end - base; + } + p += 2; + ret = pull_string_talloc(ctx, + base_ptr, + recv_flags2, + &finfo->short_name, + p, + slen, + STR_UNICODE); + if (ret == (size_t)-1) { + return pdata_end - base; + } + p += 24; /* short name */ + if (p + namelen < p || p + namelen > pdata_end) { + return pdata_end - base; + } + ret = pull_string_talloc(ctx, + base_ptr, + recv_flags2, + &finfo->name, + p, + namelen, + 0); + if (ret == (size_t)-1) { + return pdata_end - base; + } + return calc_next_entry_offset(base, pdata_end); +} + +/* Single shot SMB1 TRANS2 FindFirst. */ + +static NTSTATUS smb1_findfirst(TALLOC_CTX *mem_ctx, + struct cli_state *cli, + const char *search_name, + struct file_info **names, + size_t *num_names) +{ + NTSTATUS status; + uint16_t setup[1]; + uint8_t *param = NULL; + uint16_t recv_flags2 = 0; + uint8_t *rparam = NULL; + uint32_t num_rparam = 0; + uint8_t *rdata = NULL; + uint32_t num_rdata = 0; + uint16_t num_names_returned = 0; + struct file_info *finfo = NULL; + uint8_t *p2 = NULL; + uint8_t *data_end = NULL; + uint16_t i = 0; + + PUSH_LE_U16(&setup[0], 0, TRANSACT2_FINDFIRST); + + param = talloc_array(mem_ctx, uint8_t, 12); + if (param == NULL) { + return NT_STATUS_NO_MEMORY; + } + + PUSH_LE_U16(param, 0, FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_HIDDEN); + PUSH_LE_U16(param, 2, 1366); /* max_matches */ + PUSH_LE_U16(param, 4, FLAG_TRANS2_FIND_CLOSE_IF_END); + PUSH_LE_U16(param, 6, SMB_FIND_FILE_BOTH_DIRECTORY_INFO); /* info_level */ + + param = trans2_bytes_push_str(param, + smbXcli_conn_use_unicode(cli->conn), + search_name, + strlen(search_name)+1, + NULL); + if (param == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * A one shot SMB1 findfirst will be enough to + * return ".", "..", and "file". + */ + status = cli_trans(mem_ctx, + cli, + SMBtrans2, /* cmd */ + NULL, /* pipe_name */ + 0, /* fid */ + 0, /* function */ + 0, /* flags */ + &setup[0], + 1, /* num_setup uint16_t words */ + 0, /* max returned setup */ + param, + talloc_get_size(param), /* num_param */ + 10, /* max returned param */ + NULL, /* data */ + 0, /* num_data */ + SMB_BUFFER_SIZE_MAX, /* max retured data */ + /* Return values from here on.. */ + &recv_flags2, /* recv_flags2 */ + NULL, /* rsetup */ + 0, /* min returned rsetup */ + NULL, /* num_rsetup */ + &rparam, + 6, /* min returned rparam */ + &num_rparam, /* number of returned rparam */ + &rdata, + 0, /* min returned rdata */ + &num_rdata); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + num_names_returned = PULL_LE_U16(rparam, 2); + + finfo = talloc_array(mem_ctx, struct file_info, num_names_returned); + if (param == NULL) { + return NT_STATUS_NO_MEMORY; + } + + p2 = rdata; + data_end = rdata + num_rdata; + + for (i = 0; i < num_names_returned; i++) { + if (p2 >= data_end) { + break; + } + if (i == num_names_returned - 1) { + /* Last entry - fixup the last offset length. */ + PUSH_LE_U32(p2, 0, PTR_DIFF((rdata + num_rdata), p2)); + } + + p2 += get_filename(mem_ctx, + cli, + rdata, + recv_flags2, + p2, + data_end, + &finfo[i]); + + if (finfo->name == NULL) { + printf("%s:%d Unable to parse name from listing " + "of %s, position %u\n", + __FILE__, + __LINE__, + search_name, + (unsigned int)i); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + } + *num_names = i; + *names = finfo; + return NT_STATUS_OK; +} + +/* + * Test a specific SMB1 findfirst path to see if it + * matches a given file array. + */ +static bool test_smb1_findfirst_path(struct cli_state *cli, + const char *search_path, + struct file_info *root_finfo, + size_t num_root_finfo) +{ + size_t i = 0; + size_t num_finfo = 0; + struct file_info *finfo = NULL; + NTSTATUS status; + + status = smb1_findfirst(talloc_tos(), + cli, + search_path, + &finfo, + &num_finfo); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d smb1findfirst on %s returned %s\n", + __FILE__, + __LINE__, + search_path, + nt_errstr(status)); + return false; + } + + if (num_finfo != num_root_finfo) { + printf("%s:%d On %s, num_finfo = %zu, num_root_finfo = %zu\n", + __FILE__, + __LINE__, + search_path, + num_finfo, + num_root_finfo); + return false; + } + for (i = 0; i < num_finfo; i++) { + bool match = strequal_m(finfo[i].name, + root_finfo[i].name); + if (!match) { + printf("%s:%d Missmatch. For %s, at position %zu, " + "finfo[i].name = %s, " + "root_finfo[i].name = %s\n", + __FILE__, + __LINE__, + search_path, + i, + finfo[i].name, + root_finfo[i].name); + return false; + } + } + TALLOC_FREE(finfo); + return true; +} + +/* + * "Raw" test of doing a SMB1 findfirst to a DFS share. + * We must (mostly) use the lower level smb1cli_XXXX() interfaces, + * not the cli_XXX() ones here as the ultimate goal is to fix our + * cli_XXX() interfaces to work transparently over DFS. + * + * So here, we're testing the server code, not the client code. + * + * Passes cleanly against Windows. + */ + +bool run_smb1_dfs_search_paths(int dummy) +{ + struct cli_state *cli = NULL; + NTSTATUS status; + bool dfs_supported = false; + struct file_info *root_finfo = NULL; + size_t num_root_finfo = 0; + bool retval = false; + bool ok = false; + uint16_t fnum = (uint16_t)-1; + + printf("Starting SMB1-DFS-SEARCH-PATHS\n"); + + if (!torture_init_connection(&cli)) { + return false; + } + + if (!torture_open_connection(&cli, 0)) { + return false; + } + + /* Ensure this is a DFS share. */ + dfs_supported = smbXcli_conn_dfs_supported(cli->conn); + if (!dfs_supported) { + printf("Server %s does not support DFS\n", + smbXcli_conn_remote_name(cli->conn)); + return false; + } + dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb1.tcon); + if (!dfs_supported) { + printf("Share %s does not support DFS\n", + cli->share); + return false; + } + + /* Start clean. */ + (void)smb1_dfs_delete(cli, "BAD\\BAD\\file"); + + /* Create a test file to search for. */ + status = smb1cli_ntcreatex(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + "BAD\\BAD\\file", + OPLOCK_NONE, /* CreatFlags */ + 0, /* RootDirectoryFid */ + SEC_STD_SYNCHRONIZE| + SEC_STD_DELETE | + SEC_FILE_READ_DATA| + SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */ + 0, /* AllocationSize */ + FILE_ATTRIBUTE_NORMAL, /* FileAttributes */ + FILE_SHARE_READ| + FILE_SHARE_WRITE| + FILE_SHARE_DELETE, /* ShareAccess */ + FILE_CREATE, /* CreateDisposition */ + 0, /* CreateOptions */ + 2, /* ImpersonationLevel */ + 0, /* SecurityFlags */ + &fnum); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d smb1cli_ntcreatex on %s returned %s\n", + __FILE__, + __LINE__, + "BAD\\BAD\\file", + nt_errstr(status)); + return false; + } + + /* Close "file" handle. */ + (void)smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ + fnum = (uint16_t)-1; + + /* Get the list of files in the share. */ + status = smb1_findfirst(talloc_tos(), + cli, + "SERVER\\SHARE\\*", + &root_finfo, + &num_root_finfo); + if (!NT_STATUS_IS_OK(status)) { + printf("%s:%d smb1findfirst on %s returned %s\n", + __FILE__, + __LINE__, + "SERVER\\SHARE\\*", + nt_errstr(status)); + return false; + } + + /* + * Try different search names. They should + * all match the root directory list. + */ + ok = test_smb1_findfirst_path(cli, + "\\SERVER\\SHARE\\*", + root_finfo, + num_root_finfo); + if (!ok) { + goto err; + } + + ok = test_smb1_findfirst_path(cli, + "*", + root_finfo, + num_root_finfo); + if (!ok) { + goto err; + } + ok = test_smb1_findfirst_path(cli, + "\\*", + root_finfo, + num_root_finfo); + if (!ok) { + goto err; + } + ok = test_smb1_findfirst_path(cli, + "\\SERVER\\*", + root_finfo, + num_root_finfo); + if (!ok) { + goto err; + } + retval = true; + + err: + + if (fnum != (uint16_t)-1) { + (void)smb1cli_close(cli->conn, + cli->timeout, + cli->smb1.pid, + cli->smb1.tcon, + cli->smb1.session, + fnum, + 0); /* last_modified */ + } + + /* Delete anything we made. */ + (void)smb1_dfs_delete(cli, "BAD\\BAD\\file"); + return retval; +} diff --git a/source3/torture/torture.c b/source3/torture/torture.c index a11110cffe6..32ba9be5fb0 100644 --- a/source3/torture/torture.c +++ b/source3/torture/torture.c @@ -15361,6 +15361,14 @@ static struct { .name = "SMB2-DFS-PATHS", .fn = run_smb2_dfs_paths, }, + { + .name = "SMB1-DFS-PATHS", + .fn = run_smb1_dfs_paths, + }, + { + .name = "SMB1-DFS-SEARCH-PATHS", + .fn = run_smb1_dfs_search_paths, + }, { .name = "CLEANUP1", .fn = run_cleanup1, diff --git a/source3/torture/wscript_build b/source3/torture/wscript_build index b9241d14a40..e941829ab5f 100644 --- a/source3/torture/wscript_build +++ b/source3/torture/wscript_build @@ -35,6 +35,7 @@ bld.SAMBA3_BINARY('smbtorture' + bld.env.suffix3, test_notify_online.c test_chain3.c test_smb2.c + test_smb1_dfs.c test_authinfo_structs.c test_smbsock_any_connect.c test_cleanup.c