/* 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" #include "lib/util/time_basic.h" extern fstring host, workgroup, share, password, username, myname; extern struct cli_credentials *torture_creds; /* * Open an SMB1 file readonly and return the create time. */ static NTSTATUS get_smb1_crtime(struct cli_state *cli, const char *pathname, struct timespec *pcrtime) { NTSTATUS status; uint16_t fnum = 0; struct timespec crtime = {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 create time. 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 */ &crtime, /* create_time */ NULL, /* access_time */ NULL, /* write_time */ NULL, /* change_time */ NULL); if (NT_STATUS_IS_OK(status)) { *pcrtime = crtime; } (void)smb1cli_close(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, fnum, 0); /* last_modified */ return status; } /* * Check a crtime matches a given SMB1 path. */ static bool smb1_crtime_matches(struct cli_state *cli, const char *match_pathname, struct timespec crtime_tomatch, const char *test_pathname) { struct timespec test_crtime = { 0 }; NTSTATUS status; bool equal = false; status = get_smb1_crtime(cli, test_pathname, &test_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s: Failed to get crtime " "for %s, (%s)\n", __func__, test_pathname, nt_errstr(status)); return false; } equal = (timespec_compare(&test_crtime, &crtime_tomatch) == 0); if (!equal) { struct timeval_buf test_buf; struct timeval_buf tomatch_buf; printf("%s: crtime mismatch " "%s:crtime_tomatch=%s, %s:test_crtime = %s\n", __func__, match_pathname, timespec_string_buf(&crtime_tomatch, true, &tomatch_buf), test_pathname, timespec_string_buf(&test_crtime, true, &test_buf)); 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) { struct timespec test_timespec = { 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_crtime(cli, "BAD\\BAD\\renamed_file", &test_timespec); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime " "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_crtime(cli, src_dfs_name, &test_timespec); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime " "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) { struct timespec test_crtime = { 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_crtime(cli, "BAD\\BAD\\renamed_file", &test_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime " "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_crtime(cli, src_dfs_name, &test_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime " "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) { struct timespec test_crtime = { 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_crtime(cli, "BAD\\BAD\\renamed_file", &test_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime " "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_crtime(cli, src_dfs_name, &test_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime " "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) { struct timespec test_crtime = { 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_crtime(cli, "BAD\\BAD\\hlink", &test_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime " "for %s, (%s)\n", __FILE__, __LINE__, "BAD\\BAD\\hlink", nt_errstr(status)); goto out; } retval = smb1_crtime_matches(cli, "BAD\\BAD\\hlink", test_crtime, src_dfs_name); if (!retval) { printf("%s:%d smb1_crtime_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, struct timespec root_crtime) { char test_path[20]; const char *test_str = "/[]:|<>+=;,*?"; const char *p; unsigned int i; bool crtime_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; crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, test_path); if (!crtime_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. */ struct timespec test_crtime = { 0 }; NTSTATUS status = get_smb1_crtime(cli, test_path, &test_crtime); 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 { crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, test_path); if (!crtime_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; struct timespec root_crtime = { 0 }; struct timespec test_crtime = { 0 }; bool crtime_matched = false; bool retval = false; bool ok = false; bool equal = 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 crtime. */ status = get_smb1_crtime(cli, dfs_root_share_name, &root_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime 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. */ crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, smbXcli_conn_remote_name(cli->conn)); if (!crtime_matched) { printf("%s:%d Failed to match crtime for %s\n", __FILE__, __LINE__, smbXcli_conn_remote_name(cli->conn)); return false; } /* An "" (empty) server name should open and match the share root. */ crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, ""); if (!crtime_matched) { printf("%s:%d Failed to match crtime 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); crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, leading_backslash_name); if (!crtime_matched) { printf("%s:%d Failed to match crtime for %s\n", __FILE__, __LINE__, leading_backslash_name); return false; } } /* A "BAD" server name should open and match the share root. */ crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, "BAD"); if (!crtime_matched) { printf("%s:%d Failed to match crtime for %s\n", __FILE__, __LINE__, "BAD"); return false; } /* * A "BAD\\BAD" server and share name should open * and match the share root. */ crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, "BAD\\BAD"); if (!crtime_matched) { printf("%s:%d Failed to match crtime 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_crtime(cli, "BAD\\BAD\\BAD", &test_crtime); 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_crtime(cli, "BAD\\BAD\\BAD\\BAD", &test_crtime); 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. */ crtime_matched = smb1_crtime_matches(cli, dfs_root_share_name, root_crtime, "::::"); if (!crtime_matched) { printf("%s:%d Failed to match crtime 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_crtime); 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 crtime. */ status = get_smb1_crtime(cli, "BAD\\BAD\\file", &test_crtime); 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; } /* * This crtime must be different from the root_crtime. * This checks we're actually correctly reading crtimes * from the filesystem. */ equal = (timespec_compare(&test_crtime, &root_crtime) == 0); if (equal) { printf("%s:%d Error. crtime of %s must differ from " "root_crtime\n", __FILE__, __LINE__, "BAD\\BAD\\file"); 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(BVAL(p, 0)); p += 8; finfo->mtime_ts = interpret_long_date(BVAL(p, 0)); p += 8; finfo->ctime_ts = interpret_long_date(BVAL(p, 0)); 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 returned 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 Mismatch. 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; } static bool smb1_create_testfile(struct cli_state *cli, const char *path) { NTSTATUS status; uint16_t fnum = (uint16_t)-1; /* Create a test file. */ status = smb1cli_ntcreatex(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, path, 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__, path, 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 */ return true; } static NTSTATUS smb1_unlink(struct cli_state *cli, const char *path) { uint16_t vwv[1]; uint8_t *bytes = NULL; PUSH_LE_U16(vwv, 0, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN); bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } return cli_smb(talloc_tos(), cli, SMBunlink, /* command. */ 0, /* additional_flags. */ 1, /* wct. */ vwv, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 0, /* min_wct. */ NULL, /* return wcount. */ NULL, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ } static bool test_smb1_unlink(struct cli_state *cli) { NTSTATUS status; bool retval = false; bool ok = false; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\file"); /* Create a test file. */ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\file"); if (!ok) { printf("%s:%d failed to create test file %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\file"); goto err; } status = smb1_unlink(cli, "file"); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1unlink of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "file", nt_errstr(status)); goto err; } status = smb1_unlink(cli, "\\BAD\\file"); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1unlink of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\file", nt_errstr(status)); goto err; } status = smb1_unlink(cli, "\\BAD\\BAD\\file"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1unlink on %s returned %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\file", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\file"); return retval; } static NTSTATUS smb1_mkdir(struct cli_state *cli, const char *path) { uint8_t *bytes = NULL; bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } return cli_smb(talloc_tos(), cli, SMBmkdir, /* command. */ 0, /* additional_flags. */ 0, /* wct. */ NULL, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 0, /* min_wct. */ NULL, /* return wcount. */ NULL, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ } static bool test_smb1_mkdir(struct cli_state *cli) { NTSTATUS status; bool retval = false; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir"); status = smb1_mkdir(cli, "dir"); if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { printf("%s:%d SMB1mkdir of %s should get " "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n", __FILE__, __LINE__, "dir", nt_errstr(status)); goto err; } status = smb1_mkdir(cli, "\\BAD\\dir"); if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { printf("%s:%d SMB1mkdir of %s should get " "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n", __FILE__, __LINE__, "\\BAD\\dir", nt_errstr(status)); goto err; } status = smb1_mkdir(cli, "\\BAD\\BAD\\dir"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1mkdir on %s returned %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\dir", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir"); return retval; } static NTSTATUS smb1_rmdir(struct cli_state *cli, const char *path) { uint8_t *bytes = NULL; bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } return cli_smb(talloc_tos(), cli, SMBrmdir, /* command. */ 0, /* additional_flags. */ 0, /* wct. */ NULL, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 0, /* min_wct. */ NULL, /* return wcount. */ NULL, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ } static bool test_smb1_rmdir(struct cli_state *cli) { NTSTATUS status; bool retval = false; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir"); status = smb1_mkdir(cli, "\\BAD\\BAD\\dir"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1rmdir on %s returned %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\dir", nt_errstr(status)); goto err; } status = smb1_rmdir(cli, "dir"); if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { printf("%s:%d SMB1rmdir of %s should get " "NT_STATUS_ACCESS_DENIED, got %s\n", __FILE__, __LINE__, "dir", nt_errstr(status)); goto err; } status = smb1_rmdir(cli, "\\BAD\\dir"); if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { printf("%s:%d SMB1rmdir of %s should get " "NT_STATUS_ACCESS_DENIED, got %s\n", __FILE__, __LINE__, "\\BAD\\dir", nt_errstr(status)); goto err; } status = smb1_rmdir(cli, "\\BAD\\BAD\\dir"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1rmdir on %s returned %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\dir", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir"); return retval; } static NTSTATUS smb1_ntcreatex(struct cli_state *cli, const char *path) { NTSTATUS status; uint16_t fnum = (uint16_t)-1; status = smb1cli_ntcreatex(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, path, 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)) { return status; } /* Close "file" handle. */ (void)smb1cli_close(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, fnum, 0); /* last_modified */ return NT_STATUS_OK; } static bool test_smb1_ntcreatex(struct cli_state *cli) { NTSTATUS status; bool retval = false; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ntcreateXfile"); status = smb1_ntcreatex(cli, "ntcreateXfile"); if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { printf("%s:%d SMB1ntcreateX of %s should get " "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n", __FILE__, __LINE__, "ntcreateXfile", nt_errstr(status)); goto err; } status = smb1_ntcreatex(cli, "\\BAD\\ntcreateXfile"); if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { printf("%s:%d SMB1ntcreateX of %s should get " "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n", __FILE__, __LINE__, "\\BAD\\ntcreateXfile", nt_errstr(status)); goto err; } status = smb1_ntcreatex(cli, "\\BAD\\BAD\\ntcreateXfile"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1ntcreateX on %s returned %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\ntcreateXfile", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ntcreateXfile"); return retval; } static NTSTATUS smb1_nttrans_create(struct cli_state *cli, const char *path) { uint8_t *param = NULL; size_t converted_len = 0; uint8_t *rparam = NULL; uint32_t num_rparam = 0; uint16_t fnum = (uint16_t)-1; NTSTATUS status; param = talloc_zero_array(talloc_tos(), uint8_t, 53); if (param == NULL) { return NT_STATUS_NO_MEMORY; } param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn), path, strlen(path), &converted_len); if (param == NULL) { return NT_STATUS_NO_MEMORY; } PUSH_LE_U32(param, 8, SEC_STD_SYNCHRONIZE| SEC_STD_DELETE | SEC_FILE_READ_DATA| SEC_FILE_READ_ATTRIBUTE); /* DesiredAccess */ PUSH_LE_U32(param, 20, FILE_ATTRIBUTE_NORMAL); PUSH_LE_U32(param, 24, FILE_SHARE_READ| FILE_SHARE_WRITE| FILE_SHARE_DELETE); /* ShareAccess */ PUSH_LE_U32(param, 28, FILE_CREATE); PUSH_LE_U32(param, 44, converted_len); PUSH_LE_U32(param, 48, 0x02); /* ImpersonationLevel */ status = cli_trans(talloc_tos(), cli, SMBnttrans, /* trans cmd */ NULL, /* pipe_name */ 0, /* fid */ NT_TRANSACT_CREATE, /* function */ 0, /* flags */ NULL, /* setup */ 0, /* num_setup */ 0, /* max_setup */ param, /* param */ talloc_get_size(param), /* num_param */ 128, /* max_param */ NULL, /* data */ 0, /* num_data */ 0, /* max_data */ NULL, /* recv_flags2 */ NULL, /* rsetup */ 0, /* min_rsetup */ NULL, /* num_rsetup */ &rparam, /* rparam */ 69, /* min_rparam */ &num_rparam, /* num_rparam */ NULL, /* rdata */ 0, /* min_rdata */ NULL); /* num_rdata */ if (!NT_STATUS_IS_OK(status)) { return status; } fnum = PULL_LE_U16(param, 2); /* Close "file" handle. */ (void)smb1cli_close(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, fnum, 0); /* last_modified */ return NT_STATUS_OK; } static bool test_smb1_nttrans_create(struct cli_state *cli) { NTSTATUS status; bool retval = false; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\nttransfile"); status = smb1_nttrans_create(cli, "nttransfile"); if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { printf("%s:%d SMB1trans NT_TRANSACT_CREATE of %s should get " "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n", __FILE__, __LINE__, "nttransfile", nt_errstr(status)); goto err; } status = smb1_nttrans_create(cli, "\\BAD\\nttransfile"); if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { printf("%s:%d SMB1trans NT_TRANSACT_CREATE of %s should get " "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n", __FILE__, __LINE__, "\\BAD\\nttransfile", nt_errstr(status)); goto err; } status = smb1_nttrans_create(cli, "\\BAD\\BAD\\nttransfile"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1trans NT_TRANSACT_CREATE on %s returned %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\nttransfile", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\nttransfile"); return retval; } struct smb1_openx_state { const char *fname; uint16_t vwv[15]; uint16_t fnum; struct iovec bytes; }; static void smb1_openx_done(struct tevent_req *subreq); static struct tevent_req *smb1_openx_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, const char *path) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; uint16_t accessmode = 0; struct smb1_openx_state *state = NULL; uint8_t *bytes = NULL; NTSTATUS status; req = tevent_req_create(mem_ctx, &state, struct smb1_openx_state); if (req == NULL) { return NULL; } accessmode = (DENY_NONE<<4); accessmode |= DOS_OPEN_RDONLY; PUSH_LE_U8(state->vwv + 0, 0, 0xFF); PUSH_LE_U16(state->vwv + 3, 0, accessmode); PUSH_LE_U16(state->vwv + 4, 0, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY); PUSH_LE_U16(state->vwv + 8, 0, OPENX_FILE_CREATE_IF_NOT_EXIST| OPENX_FILE_EXISTS_FAIL); bytes = talloc_array(state, uint8_t, 0); if (tevent_req_nomem(bytes, req)) { return tevent_req_post(req, ev); } bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (tevent_req_nomem(bytes, req)) { return tevent_req_post(req, ev); } state->bytes.iov_base = (void *)bytes; state->bytes.iov_len = talloc_get_size(bytes); subreq = cli_smb_req_create(state, ev, cli, SMBopenX, /* cmd */ 0, /* additional_flags */ 0, /* additional_flags2 */ 15, /* num_vwv */ state->vwv, /* vwv */ 1, /* iovcount */ &state->bytes); /* iovec */ if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, smb1_openx_done, req); status = smb1cli_req_chain_submit(&subreq, 1); if (tevent_req_nterror(req, status)) { return tevent_req_post(req, ev); } return req; } static void smb1_openx_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct smb1_openx_state *state = tevent_req_data( req, struct smb1_openx_state); uint8_t wct = 0; uint16_t *vwv = NULL; NTSTATUS status; status = cli_smb_recv(subreq, state, NULL, /* pinbuf */ 3, /* min_wct */ &wct, /* wct */ &vwv, /* vwv */ NULL, /* num_rbytes */ NULL); /* rbytes */ TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } state->fnum = PULL_LE_U16(vwv+2, 0); tevent_req_done(req); } static NTSTATUS smb1_openx_recv(struct tevent_req *req, uint16_t *pfnum) { struct smb1_openx_state *state = tevent_req_data( req, struct smb1_openx_state); NTSTATUS status; if (tevent_req_is_nterror(req, &status)) { return status; } *pfnum = state->fnum; return NT_STATUS_OK; } static NTSTATUS smb1_openx(struct cli_state *cli, const char *path) { TALLOC_CTX *frame = talloc_stackframe(); struct tevent_context *ev = NULL; struct tevent_req *req = NULL; uint16_t fnum = (uint16_t)-1; NTSTATUS status = NT_STATUS_NO_MEMORY; ev = samba_tevent_context_init(frame); if (ev == NULL) { goto fail; } req = smb1_openx_send(frame, ev, cli, path); if (req == NULL) { goto fail; } if (!tevent_req_poll_ntstatus(req, ev, &status)) { goto fail; } status = smb1_openx_recv(req, &fnum); fail: /* Close "file" handle. */ 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 */ } TALLOC_FREE(frame); return status; } static bool test_smb1_openx(struct cli_state *cli) { NTSTATUS status; bool retval = false; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\openxfile"); status = smb1_openx(cli, "openxfile"); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1openx of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "openxfile", nt_errstr(status)); goto err; } status = smb1_openx(cli, "\\BAD\\openxfile"); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1openx of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\openxfile", nt_errstr(status)); goto err; } status = smb1_openx(cli, "\\BAD\\BAD\\openxfile"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1openx on %s returned %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\openxfile", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\openxfile"); return retval; } static NTSTATUS smb1_open(struct cli_state *cli, const char *path, uint16_t *pfnum) { uint16_t vwv[2] = { 0, 0}; uint8_t *bytes = NULL; uint16_t accessmode = 0; uint16_t *return_words = NULL; uint8_t return_wcount = 0; NTSTATUS status; accessmode = (DENY_NONE<<4); accessmode |= DOS_OPEN_RDONLY; PUSH_LE_U16(vwv + 0, 0, accessmode); PUSH_LE_U16(vwv + 1, 0, FILE_ATTRIBUTE_NORMAL); bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } status = cli_smb(talloc_tos(), cli, SMBopen, /* command. */ 0, /* additional_flags. */ 2, /* wct. */ vwv, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 7, /* min_wct. */ &return_wcount, /* return wcount. */ &return_words, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ if (!NT_STATUS_IS_OK(status)) { return status; } *pfnum = PULL_LE_U16(return_words, 0); return status; } static bool test_smb1_open(struct cli_state *cli) { NTSTATUS status; bool retval = false; bool ok = false; bool equal = false; uint16_t fnum = (uint16_t)-1; struct timespec testfile_crtime = { 0 }; struct timespec open_crtime = { 0 }; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\openfile"); /* Create a test file. */ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\openfile"); if (!ok) { printf("%s:%d failed to create test file %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\openfile"); goto err; } /* Get the test file crtime number. */ status = get_smb1_crtime(cli, "\\BAD\\BAD\\openfile", &testfile_crtime); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to get crtime for %s, (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\openfile", nt_errstr(status)); goto err; } status = smb1_open(cli, "openfile", &fnum); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1open of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "openfile", nt_errstr(status)); goto err; } status = smb1_open(cli, "\\BAD\\openfile", &fnum); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1open of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\openfile", nt_errstr(status)); goto err; } status = smb1_open(cli, "\\BAD\\BAD\\openfile", &fnum); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d failed to open test file %s (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\openfile", nt_errstr(status)); goto err; } status = cli_qfileinfo_basic(cli, fnum, NULL, /* attr */ NULL, /* size */ &open_crtime, /* create_time */ NULL, /* access_time */ NULL, /* write_time */ NULL, /* change_time */ NULL); /* ino */ if (!NT_STATUS_IS_OK(status)) { printf("%s:%d failed to get crtime of test file %s (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\openfile", nt_errstr(status)); goto err; } equal = (timespec_compare(&testfile_crtime, &open_crtime) == 0); if (!equal) { printf("%s:%d crtime mismatch of test file %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\openfile"); goto err; } retval = true; err: /* Close "openfile" handle. */ 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\\openfile"); return retval; } static NTSTATUS smb1_create(struct cli_state *cli, const char *path, uint16_t smb1_operation, uint16_t *pfnum) { uint16_t vwv[3] = { 0, 0, 0}; uint8_t *bytes = NULL; uint16_t *return_words = NULL; uint8_t return_wcount = 0; NTSTATUS status; PUSH_LE_U16(vwv + 0, 0, FILE_ATTRIBUTE_NORMAL); bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } status = cli_smb(talloc_tos(), cli, smb1_operation, /* command. */ 0, /* additional_flags. */ 3, /* wct. */ vwv, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 1, /* min_wct. */ &return_wcount, /* return wcount. */ &return_words, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ if (!NT_STATUS_IS_OK(status)) { return status; } *pfnum = PULL_LE_U16(return_words, 0); return status; } static bool test_smb1_create(struct cli_state *cli) { NTSTATUS status; bool retval = false; uint16_t fnum = (uint16_t)-1; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\createfile"); (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\mknewfile"); status = smb1_create(cli, "createfile", SMBcreate, &fnum); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1create of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "createfile", nt_errstr(status)); goto err; } status = smb1_create(cli, "\\BAD\\createfile", SMBcreate, &fnum); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1open of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\openfile", nt_errstr(status)); goto err; } status = smb1_create(cli, "\\BAD\\BAD\\createfile", SMBcreate, &fnum); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d failed to create file %s (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\createfile", nt_errstr(status)); goto err; } (void)smb1cli_close(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, fnum, 0); /* last_modified */ fnum = (uint16_t)-1; /* Now do the same with SMBmknew */ status = smb1_create(cli, "mknewfile", SMBmknew, &fnum); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1mknew of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "mknewfile", nt_errstr(status)); goto err; } status = smb1_create(cli, "\\BAD\\mknewfile", SMBmknew, &fnum); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1mknew of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\mknewfile", nt_errstr(status)); goto err; } status = smb1_create(cli, "\\BAD\\BAD\\mknewfile", SMBmknew, &fnum); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d failed to create file %s (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\mknewfile", nt_errstr(status)); goto err; } (void)smb1cli_close(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, fnum, 0); /* last_modified */ fnum = (uint16_t)-1; retval = true; err: /* Close "openfile" handle. */ 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\\createfile"); (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\mknewfile"); return retval; } static NTSTATUS smb1_getatr(struct cli_state *cli, const char *path, uint16_t *pattr) { uint8_t *bytes = NULL; uint16_t *return_words = NULL; uint8_t return_wcount = 0; NTSTATUS status; bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } status = cli_smb(talloc_tos(), cli, SMBgetatr, /* command. */ 0, /* additional_flags. */ 0, /* wct. */ NULL, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 10, /* min_wct. */ &return_wcount, /* return wcount. */ &return_words, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ if (!NT_STATUS_IS_OK(status)) { return status; } *pattr = PULL_LE_U16(return_words, 0); return status; } static bool test_smb1_getatr(struct cli_state *cli) { NTSTATUS status; bool retval = false; bool ok = false; uint16_t attrs = 0; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\getatrfile"); /* Create a test file. */ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\getatrfile"); if (!ok) { printf("%s:%d failed to create test file %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\getatrfile"); goto err; } /* * We expect this to succeed, but get attributes of * the root directory. */ status = smb1_getatr(cli, "getatrfile", &attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1getatr of %s failed (%s)\n", __FILE__, __LINE__, "getatrfile", nt_errstr(status)); goto err; } if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) { printf("%s:%d error expected SMB1getatr of file %s " "to return directory attributes. Got 0x%x\n", __FILE__, __LINE__, "getatrfile", (unsigned int)attrs); goto err; } /* * We expect this to succeed, but get attributes of * the root directory. */ status = smb1_getatr(cli, "\\BAD\\getatrfile", &attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1getatr of %s failed (%s)\n", __FILE__, __LINE__, "\\BAD\\getatrfile", nt_errstr(status)); goto err; } if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) { printf("%s:%d error expected SMB1getatr of file %s " "to return directory attributes. Got 0x%x\n", __FILE__, __LINE__, "\\BAD\\getatrfile", (unsigned int)attrs); goto err; } /* * We expect this to succeed, and get attributes of * the testfile. */ status = smb1_getatr(cli, "\\BAD\\BAD\\getatrfile", &attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1getatr of %s failed (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\getatrfile", nt_errstr(status)); goto err; } if (attrs & FILE_ATTRIBUTE_DIRECTORY) { printf("%s:%d error expected SMB1getatr of file %s " "to return non-directory attributes. Got 0x%x\n", __FILE__, __LINE__, "\\BAD\\BAD\\getatrfile", (unsigned int)attrs); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\getatrfile"); return retval; } static NTSTATUS smb1_setatr(struct cli_state *cli, const char *path, uint16_t attr) { uint16_t vwv[8] = { 0 }; uint8_t *bytes = NULL; NTSTATUS status; PUSH_LE_U16(vwv, 0, attr); bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } status = cli_smb(talloc_tos(), cli, SMBsetatr, /* command. */ 0, /* additional_flags. */ 8, /* wct. */ vwv, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 0, /* min_wct. */ NULL, /* return wcount. */ NULL, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ if (!NT_STATUS_IS_OK(status)) { return status; } return status; } static bool test_smb1_setatr(struct cli_state *cli) { NTSTATUS status; bool retval = false; bool ok = false; uint16_t file_attrs = 0; uint16_t orig_file_attrs = 0; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\setatrfile"); /* Create a test file. */ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\setatrfile"); if (!ok) { printf("%s:%d failed to create test file %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\setatrfile"); goto err; } /* Get it's original attributes. */ status = smb1_getatr(cli, "\\BAD\\BAD\\setatrfile", &orig_file_attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1getatr of %s failed (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\setatrfile", nt_errstr(status)); goto err; } if (orig_file_attrs & FILE_ATTRIBUTE_SYSTEM) { printf("%s:%d orig_file_attrs of %s already has SYSTEM. " "Test cannot proceed.\n", __FILE__, __LINE__, "\\BAD\\BAD\\setatrfile"); goto err; } /* * Seems we can't set attrs on the root of a share, * even as Administrator. */ status = smb1_setatr(cli, "setatrfile", FILE_ATTRIBUTE_SYSTEM); if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { printf("%s:%d SMB1setatr of %s should get " "NT_STATUS_ACCESS_DENIED, got %s\n", __FILE__, __LINE__, "setatrfile", nt_errstr(status)); goto err; } /* * Seems we can't set attrs on the root of a share, * even as Administrator. */ status = smb1_setatr(cli, "\\BAD\\setatrfile", FILE_ATTRIBUTE_SYSTEM); if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { printf("%s:%d SMB1setatr of %s should get " "NT_STATUS_ACCESS_DENIED, got %s\n", __FILE__, __LINE__, "\\BAD\\setatrfile", nt_errstr(status)); goto err; } status = smb1_setatr(cli, "\\BAD\\BAD\\setatrfile", FILE_ATTRIBUTE_SYSTEM); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1setatr of %s failed (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\setatrfile", nt_errstr(status)); goto err; } status = smb1_getatr(cli, "\\BAD\\BAD\\setatrfile", &file_attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1getatr of %s failed (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\setatrfile", nt_errstr(status)); goto err; } if (file_attrs != FILE_ATTRIBUTE_SYSTEM) { printf("%s:%d Failed to set SYSTEM attr on %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\setatrfile"); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\setatrfile"); return retval; } static NTSTATUS smb1_chkpath(struct cli_state *cli, const char *path) { uint8_t *bytes = NULL; NTSTATUS status; bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } status = cli_smb(talloc_tos(), cli, SMBcheckpath, /* command. */ 0, /* additional_flags. */ 0, /* wct. */ NULL, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 0, /* min_wct. */ NULL, /* return wcount. */ NULL, /* return wvw. */ NULL, /* return byte count. */ NULL); /* return bytes. */ if (!NT_STATUS_IS_OK(status)) { return status; } return status; } static bool test_smb1_chkpath(struct cli_state *cli) { NTSTATUS status; bool retval = false; bool ok = false; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\chkpathfile"); /* Create a test file. */ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\chkpathfile"); if (!ok) { printf("%s:%d failed to create test file %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\chkpathfile"); goto err; } /* * Should succeed - "chkpathfile" maps to * directory "". */ status = smb1_chkpath(cli, "chkpathfile"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1chkpath of %s failed (%s)\n", __FILE__, __LINE__, "chkpathfile", nt_errstr(status)); goto err; } /* * Should succeed - "\\BAD\\chkpathfile" maps to * directory "". */ status = smb1_chkpath(cli, "\\BAD\\chkpathfile"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1chkpath of %s failed (%s)\n", __FILE__, __LINE__, "\\BAD\\chkpathfile", nt_errstr(status)); goto err; } /* * Should fail - "\\BAD\\BAD\\chkpathfile" maps to the * "\\BAD\\BAD\\chkpathfile", not a directory. */ status = smb1_chkpath(cli, "\\BAD\\BAD\\chkpathfile"); if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY)) { printf("%s:%d SMB1chkpath of %s should get " "NT_STATUS_NOT_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\chkpathfile", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\chkpathfile"); return retval; } /* * Test BUG: https://bugzilla.samba.org/show_bug.cgi?id=15419 */ static bool test_smb1_chkpath_bad(struct cli_state *cli) { NTSTATUS status; status = smb1_chkpath(cli, "\\x//\\/"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d SMB1chkpath of %s failed (%s)\n", __FILE__, __LINE__, "\\x//\\/", nt_errstr(status)); return false; } return true; } static NTSTATUS smb1_ctemp(struct cli_state *cli, const char *path, char **tmp_path) { uint16_t vwv[3] = { 0 }; uint8_t *bytes = NULL; NTSTATUS status; uint16_t *return_words = NULL; uint8_t return_wcount = 0; uint32_t return_bytecount = 0; uint8_t *return_bytes = NULL; size_t sret = 0; uint16_t fnum = (uint16_t)-1; bytes = talloc_array(talloc_tos(), uint8_t, 1); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } bytes[0] = 4; bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), path, strlen(path)+1, NULL); if (bytes == NULL) { return NT_STATUS_NO_MEMORY; } status = cli_smb(talloc_tos(), cli, SMBctemp, /* command. */ 0, /* additional_flags. */ 3, /* wct. */ vwv, /* vwv. */ talloc_get_size(bytes), /* num_bytes. */ bytes, /* bytes. */ NULL, /* result parent. */ 1, /* min_wct. */ &return_wcount, /* return wcount. */ &return_words, /* return wvw. */ &return_bytecount, /* return byte count. */ &return_bytes); /* return bytes. */ if (!NT_STATUS_IS_OK(status)) { return status; } if (return_wcount != 1) { return NT_STATUS_INVALID_NETWORK_RESPONSE; } fnum = PULL_LE_U16(return_words, 0); /* Delete the file by fnum. */ status = cli_nt_delete_on_close(cli, fnum, 1); if (!NT_STATUS_IS_OK(status)) { return status; } (void)smb1cli_close(cli->conn, cli->timeout, cli->smb1.pid, cli->smb1.tcon, cli->smb1.session, fnum, 0); /* last_modified */ fnum = (uint16_t)-1; if (return_bytecount < 2) { return NT_STATUS_DATA_ERROR; } sret = pull_string_talloc(talloc_tos(), NULL, 0, tmp_path, return_bytes, return_bytecount, STR_ASCII); if (sret == 0) { return NT_STATUS_NO_MEMORY; } return status; } static bool test_smb1_ctemp(struct cli_state *cli) { NTSTATUS status; bool retval = false; char *retpath = NULL; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ctemp_dir"); status = smb1_mkdir(cli, "\\BAD\\BAD\\ctemp_dir"); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d Failed to create %s (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\ctemp_dir", nt_errstr(status)); goto err; } /* * Windows returns NT_STATUS_FILE_IS_A_DIRECTORY * for all SMBctemp calls on a DFS share, no * matter what we put in the pathname. */ /* * When we fix smbd we'll need to detect running * in smbtorture3 against smbd here and modify * the expected behavior. Windows is simply * broken here. */ status = smb1_ctemp(cli, "ctemp_dir", &retpath); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1ctemp of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "ctemp_dir", nt_errstr(status)); goto err; } status = smb1_ctemp(cli, "\\BAD\\ctemp_dir", &retpath); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1ctemp of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\ctemp_dir", nt_errstr(status)); goto err; } status = smb1_ctemp(cli, "\\BAD\\BAD\\ctemp_dir", &retpath); if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) { printf("%s:%d SMB1ctemp of %s should get " "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\ctemp_dir", nt_errstr(status)); goto err; } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ctemp_dir"); return retval; } static NTSTATUS smb1_qpathinfo(struct cli_state *cli, const char *fname, uint32_t *pattrs) { NTSTATUS status; uint8_t *param = NULL; uint16_t setup[1] = { 0 }; uint8_t *rdata = NULL; uint32_t num_rdata = 0; PUSH_LE_U16(setup, 0, TRANSACT2_QPATHINFO); param = talloc_zero_array(talloc_tos(), uint8_t, 6); if (param == NULL) { return NT_STATUS_NO_MEMORY; } PUSH_LE_U16(param, 0, SMB_QUERY_FILE_BASIC_INFO); param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn), fname, strlen(fname)+1, NULL); if (param == NULL) { return NT_STATUS_NO_MEMORY; } status = cli_trans(talloc_tos(), 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 */ 2, /* max returned param */ NULL, /* data */ 0, /* num_data */ SMB_BUFFER_SIZE_MAX, /* max returned data */ /* Return values from here on.. */ NULL, /* recv_flags2 */ NULL, /* rsetup */ 0, /* min returned rsetup */ NULL, /* num_rsetup */ NULL, 0, /* min returned rparam */ NULL, /* number of returned rparam */ &rdata, 36, /* min returned rdata */ &num_rdata); if (!NT_STATUS_IS_OK(status)) { return status; } *pattrs = PULL_LE_U32(rdata, 32); return NT_STATUS_OK; } static bool test_smb1_qpathinfo(struct cli_state *cli) { NTSTATUS status; bool retval = false; bool ok = false; uint32_t attrs; /* Start clean. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\qpathinfo_file"); /* Create a test file. */ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\qpathinfo_file"); if (!ok) { printf("%s:%d failed to create test file %s\n", __FILE__, __LINE__, "\\BAD\\BAD\\qpathinfo_file"); goto err; } /* Should get root dir attrs. */ status = smb1_qpathinfo(cli, "qpathinfo_file", &attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d smb1_qpathinfo failed %s (%s)\n", __FILE__, __LINE__, "qpathinfo_file", nt_errstr(status)); goto err; } if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) { printf("%s:%d expected FILE_ATTRIBUTE_DIRECTORY on %s " "got attribute 0x%x\n", __FILE__, __LINE__, "qpathinfo_file", (unsigned int)attrs); goto err; } /* Should get root dir attrs. */ status = smb1_qpathinfo(cli, "\\BAD\\qpathinfo_file", &attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d smb1_qpathinfo failed %s (%s)\n", __FILE__, __LINE__, "\\BAD\\qpathinfo_file", nt_errstr(status)); goto err; } if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) { printf("%s:%d expected FILE_ATTRIBUTE_DIRECTORY on %s " "got attribute 0x%x\n", __FILE__, __LINE__, "\\BAD\\qpathinfo_file", (unsigned int)attrs); goto err; } /* Should get file attrs. */ status = smb1_qpathinfo(cli, "\\BAD\\BAD\\qpathinfo_file", &attrs); if (!NT_STATUS_IS_OK(status)) { printf("%s:%d smb1_qpathinfo failed %s (%s)\n", __FILE__, __LINE__, "\\BAD\\BAD\\qpathinfo_file", nt_errstr(status)); goto err; } if ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0) { printf("%s:%d expected not FILE_ATTRIBUTE_DIRECTORY on %s " "got attribute 0x%x\n", __FILE__, __LINE__, "\\BAD\\BAD\\qpathinfo_file", (unsigned int)attrs); } retval = true; err: (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\qpathinfo_file"); return retval; } /* * "Raw" test of different SMB1 operations 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_operations(int dummy) { struct cli_state *cli = NULL; bool dfs_supported = false; bool retval = false; bool ok = false; printf("Starting SMB1-DFS-OPS\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; } ok = test_smb1_unlink(cli); if (!ok) { goto err; } ok = test_smb1_mkdir(cli); if (!ok) { goto err; } ok = test_smb1_rmdir(cli); if (!ok) { goto err; } ok = test_smb1_ntcreatex(cli); if (!ok) { goto err; } ok = test_smb1_nttrans_create(cli); if (!ok) { goto err; } ok = test_smb1_openx(cli); if (!ok) { goto err; } ok = test_smb1_open(cli); if (!ok) { goto err; } ok = test_smb1_create(cli); if (!ok) { goto err; } ok = test_smb1_getatr(cli); if (!ok) { goto err; } ok = test_smb1_setatr(cli); if (!ok) { goto err; } ok = test_smb1_chkpath(cli); if (!ok) { goto err; } ok = test_smb1_ctemp(cli); if (!ok) { goto err; } ok = test_smb1_qpathinfo(cli); if (!ok) { goto err; } retval = true; err: /* Delete anything we made. */ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\file"); return retval; } /* * Test BUG: https://bugzilla.samba.org/show_bug.cgi?id=15419 */ bool run_smb1_dfs_check_badpath(int dummy) { struct cli_state *cli = NULL; bool dfs_supported = false; printf("Starting SMB1-DFS-CHECK-BADPATH\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; } return test_smb1_chkpath_bad(cli); }