diff options
Diffstat (limited to 'channels/drive/client')
-rw-r--r-- | channels/drive/client/CMakeLists.txt | 29 | ||||
-rw-r--r-- | channels/drive/client/drive_file.c | 971 | ||||
-rw-r--r-- | channels/drive/client/drive_file.h | 67 | ||||
-rw-r--r-- | channels/drive/client/drive_main.c | 1120 |
4 files changed, 2187 insertions, 0 deletions
diff --git a/channels/drive/client/CMakeLists.txt b/channels/drive/client/CMakeLists.txt new file mode 100644 index 0000000..a236f76 --- /dev/null +++ b/channels/drive/client/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("drive") + +set(${MODULE_PREFIX}_SRCS + drive_file.c + drive_file.h + drive_main.c +) + +set(${MODULE_PREFIX}_LIBS + winpr freerdp +) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") diff --git a/channels/drive/client/drive_file.c b/channels/drive/client/drive_file.c new file mode 100644 index 0000000..31150ea --- /dev/null +++ b/channels/drive/client/drive_file.c @@ -0,0 +1,971 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <winpr/wtypes.h> +#include <winpr/crt.h> +#include <winpr/string.h> +#include <winpr/path.h> +#include <winpr/file.h> +#include <winpr/stream.h> + +#include <freerdp/channels/rdpdr.h> + +#include "drive_file.h" + +#ifdef WITH_DEBUG_RDPDR +#define DEBUG_WSTR(msg, wstr) \ + do \ + { \ + char lpstr[1024] = { 0 }; \ + ConvertWCharToUtf8(wstr, lpstr, ARRAYSIZE(lpstr)); \ + WLog_DBG(TAG, msg, lpstr); \ + } while (0) +#else +#define DEBUG_WSTR(msg, wstr) \ + do \ + { \ + } while (0) +#endif + +static BOOL drive_file_fix_path(WCHAR* path, size_t length) +{ + if ((length == 0) || (length > UINT32_MAX)) + return FALSE; + + WINPR_ASSERT(path); + + for (size_t i = 0; i < length; i++) + { + if (path[i] == L'\\') + path[i] = L'/'; + } + +#ifdef WIN32 + + if ((length == 3) && (path[1] == L':') && (path[2] == L'/')) + return FALSE; + +#else + + if ((length == 1) && (path[0] == L'/')) + return FALSE; + +#endif + + if ((length > 0) && (path[length - 1] == L'/')) + path[length - 1] = L'\0'; + + return TRUE; +} + +static WCHAR* drive_file_combine_fullpath(const WCHAR* base_path, const WCHAR* path, + size_t PathWCharLength) +{ + BOOL ok = FALSE; + WCHAR* fullpath = NULL; + size_t length = 0; + + if (!base_path || (!path && (PathWCharLength > 0))) + goto fail; + + const size_t base_path_length = _wcsnlen(base_path, MAX_PATH); + length = base_path_length + PathWCharLength + 1; + fullpath = (WCHAR*)calloc(length, sizeof(WCHAR)); + + if (!fullpath) + goto fail; + + CopyMemory(fullpath, base_path, base_path_length * sizeof(WCHAR)); + if (path) + CopyMemory(&fullpath[base_path_length], path, PathWCharLength * sizeof(WCHAR)); + + if (!drive_file_fix_path(fullpath, length)) + goto fail; + + /* Ensure the path does not contain sequences like '..' */ + WCHAR dotdotbuffer[6] = { 0 }; + const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer)); + if (_wcsstr(&fullpath[base_path_length], dotdot)) + { + char abuffer[MAX_PATH] = { 0 }; + ConvertWCharToUtf8(&fullpath[base_path_length], abuffer, ARRAYSIZE(abuffer)); + + WLog_WARN(TAG, "[rdpdr] received invalid file path '%s' from server, aborting!", + &abuffer[base_path_length]); + goto fail; + } + + ok = TRUE; +fail: + if (!ok) + { + free(fullpath); + fullpath = NULL; + } + return fullpath; +} + +static BOOL drive_file_set_fullpath(DRIVE_FILE* file, WCHAR* fullpath) +{ + if (!file || !fullpath) + return FALSE; + + const size_t len = _wcslen(fullpath); + free(file->fullpath); + file->fullpath = NULL; + + if (len == 0) + return TRUE; + + file->fullpath = fullpath; + + const WCHAR sep[] = { PathGetSeparatorW(PATH_STYLE_NATIVE), '\0' }; + WCHAR* filename = _wcsrchr(file->fullpath, *sep); + if (filename && _wcsncmp(filename, sep, ARRAYSIZE(sep)) == 0) + *filename = '\0'; + + return TRUE; +} + +static BOOL drive_file_init(DRIVE_FILE* file) +{ + UINT CreateDisposition = 0; + DWORD dwAttr = GetFileAttributesW(file->fullpath); + + if (dwAttr != INVALID_FILE_ATTRIBUTES) + { + /* The file exists */ + file->is_dir = (dwAttr & FILE_ATTRIBUTE_DIRECTORY) != 0; + + if (file->is_dir) + { + if (file->CreateDisposition == FILE_CREATE) + { + SetLastError(ERROR_ALREADY_EXISTS); + return FALSE; + } + + if (file->CreateOptions & FILE_NON_DIRECTORY_FILE) + { + SetLastError(ERROR_ACCESS_DENIED); + return FALSE; + } + + return TRUE; + } + else + { + if (file->CreateOptions & FILE_DIRECTORY_FILE) + { + SetLastError(ERROR_DIRECTORY); + return FALSE; + } + } + } + else + { + file->is_dir = ((file->CreateOptions & FILE_DIRECTORY_FILE) ? TRUE : FALSE); + + if (file->is_dir) + { + /* Should only create the directory if the disposition allows for it */ + if ((file->CreateDisposition == FILE_OPEN_IF) || + (file->CreateDisposition == FILE_CREATE)) + { + if (CreateDirectoryW(file->fullpath, NULL) != 0) + { + return TRUE; + } + } + + SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + } + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + switch (file->CreateDisposition) + { + case FILE_SUPERSEDE: /* If the file already exists, replace it with the given file. If + it does not, create the given file. */ + CreateDisposition = CREATE_ALWAYS; + break; + + case FILE_OPEN: /* If the file already exists, open it instead of creating a new file. + If it does not, fail the request and do not create a new file. */ + CreateDisposition = OPEN_EXISTING; + break; + + case FILE_CREATE: /* If the file already exists, fail the request and do not create or + open the given file. If it does not, create the given file. */ + CreateDisposition = CREATE_NEW; + break; + + case FILE_OPEN_IF: /* If the file already exists, open it. If it does not, create the + given file. */ + CreateDisposition = OPEN_ALWAYS; + break; + + case FILE_OVERWRITE: /* If the file already exists, open it and overwrite it. If it does + not, fail the request. */ + CreateDisposition = TRUNCATE_EXISTING; + break; + + case FILE_OVERWRITE_IF: /* If the file already exists, open it and overwrite it. If it + does not, create the given file. */ + CreateDisposition = CREATE_ALWAYS; + break; + + default: + break; + } + +#ifndef WIN32 + file->SharedAccess = 0; +#endif + file->file_handle = CreateFileW(file->fullpath, file->DesiredAccess, file->SharedAccess, + NULL, CreateDisposition, file->FileAttributes, NULL); + } + +#ifdef WIN32 + if (file->file_handle == INVALID_HANDLE_VALUE) + { + /* Get the error message, if any. */ + DWORD errorMessageID = GetLastError(); + + if (errorMessageID != 0) + { + LPSTR messageBuffer = NULL; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, NULL); + WLog_ERR(TAG, "Error in drive_file_init: %s %s", messageBuffer, file->fullpath); + /* Free the buffer. */ + LocalFree(messageBuffer); + /* restore original error code */ + SetLastError(errorMessageID); + } + } +#endif + + return file->file_handle != INVALID_HANDLE_VALUE; +} + +DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength, + UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition, + UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess) +{ + DRIVE_FILE* file = NULL; + + if (!base_path || (!path && (PathWCharLength > 0))) + return NULL; + + file = (DRIVE_FILE*)calloc(1, sizeof(DRIVE_FILE)); + + if (!file) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + file->file_handle = INVALID_HANDLE_VALUE; + file->find_handle = INVALID_HANDLE_VALUE; + file->id = id; + file->basepath = base_path; + file->FileAttributes = FileAttributes; + file->DesiredAccess = DesiredAccess; + file->CreateDisposition = CreateDisposition; + file->CreateOptions = CreateOptions; + file->SharedAccess = SharedAccess; + drive_file_set_fullpath(file, drive_file_combine_fullpath(base_path, path, PathWCharLength)); + + if (!drive_file_init(file)) + { + DWORD lastError = GetLastError(); + drive_file_free(file); + SetLastError(lastError); + return NULL; + } + + return file; +} + +BOOL drive_file_free(DRIVE_FILE* file) +{ + BOOL rc = FALSE; + + if (!file) + return FALSE; + + if (file->file_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(file->file_handle); + file->file_handle = INVALID_HANDLE_VALUE; + } + + if (file->find_handle != INVALID_HANDLE_VALUE) + { + FindClose(file->find_handle); + file->find_handle = INVALID_HANDLE_VALUE; + } + + if (file->delete_pending) + { + if (file->is_dir) + { + if (!winpr_RemoveDirectory_RecursiveW(file->fullpath)) + goto fail; + } + else if (!DeleteFileW(file->fullpath)) + goto fail; + } + + rc = TRUE; +fail: + DEBUG_WSTR("Free %s", file->fullpath); + free(file->fullpath); + free(file); + return rc; +} + +BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset) +{ + LARGE_INTEGER loffset; + + if (!file) + return FALSE; + + if (Offset > INT64_MAX) + return FALSE; + + loffset.QuadPart = (LONGLONG)Offset; + return SetFilePointerEx(file->file_handle, loffset, NULL, FILE_BEGIN); +} + +BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length) +{ + DWORD read = 0; + + if (!file || !buffer || !Length) + return FALSE; + + DEBUG_WSTR("Read file %s", file->fullpath); + + if (ReadFile(file->file_handle, buffer, *Length, &read, NULL)) + { + *Length = read; + return TRUE; + } + + return FALSE; +} + +BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, UINT32 Length) +{ + DWORD written = 0; + + if (!file || !buffer) + return FALSE; + + DEBUG_WSTR("Write file %s", file->fullpath); + + while (Length > 0) + { + if (!WriteFile(file->file_handle, buffer, Length, &written, NULL)) + return FALSE; + + Length -= written; + buffer += written; + } + + return TRUE; +} + +static BOOL drive_file_query_from_handle_information(const DRIVE_FILE* file, + const BY_HANDLE_FILE_INFORMATION* info, + UINT32 FsInformationClass, wStream* output) +{ + switch (FsInformationClass) + { + case FileBasicInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 36)) + return FALSE; + + Stream_Write_UINT32(output, 36); /* Length */ + Stream_Write_UINT32(output, info->ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, info->ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, info->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, info->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */ + /* Reserved(4), MUST NOT be added! */ + break; + + case FileStandardInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 22)) + return FALSE; + + Stream_Write_UINT32(output, 22); /* Length */ + Stream_Write_UINT32(output, info->nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, info->nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, info->nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, info->nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, info->nNumberOfLinks); /* NumberOfLinks */ + Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */ + Stream_Write_UINT8(output, info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY + ? TRUE + : FALSE); /* Directory */ + /* Reserved(2), MUST NOT be added! */ + break; + + case FileAttributeTagInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 8)) + return FALSE; + + Stream_Write_UINT32(output, 8); /* Length */ + Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, 0); /* ReparseTag */ + break; + + default: + /* Unhandled FsInformationClass */ + return FALSE; + } + + return TRUE; +} + +static BOOL drive_file_query_from_attributes(const DRIVE_FILE* file, + const WIN32_FILE_ATTRIBUTE_DATA* attrib, + UINT32 FsInformationClass, wStream* output) +{ + switch (FsInformationClass) + { + case FileBasicInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 36)) + return FALSE; + + Stream_Write_UINT32(output, 36); /* Length */ + Stream_Write_UINT32(output, attrib->ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, attrib->ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + attrib->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + attrib->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */ + /* Reserved(4), MUST NOT be added! */ + break; + + case FileStandardInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 22)) + return FALSE; + + Stream_Write_UINT32(output, 22); /* Length */ + Stream_Write_UINT32(output, attrib->nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, attrib->nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, 0); /* NumberOfLinks */ + Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */ + Stream_Write_UINT8(output, attrib->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY + ? TRUE + : FALSE); /* Directory */ + /* Reserved(2), MUST NOT be added! */ + break; + + case FileAttributeTagInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 8)) + return FALSE; + + Stream_Write_UINT32(output, 8); /* Length */ + Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, 0); /* ReparseTag */ + break; + + default: + /* Unhandled FsInformationClass */ + return FALSE; + } + + return TRUE; +} + +BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output) +{ + BY_HANDLE_FILE_INFORMATION fileInformation = { 0 }; + BOOL status = 0; + HANDLE hFile = NULL; + + if (!file || !output) + return FALSE; + + hFile = CreateFileW(file->fullpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + status = GetFileInformationByHandle(hFile, &fileInformation); + CloseHandle(hFile); + if (!status) + goto out_fail; + + if (!drive_file_query_from_handle_information(file, &fileInformation, FsInformationClass, + output)) + goto out_fail; + + return TRUE; + } + + /* If we failed before (i.e. if information for a drive is queried) fall back to + * GetFileAttributesExW */ + WIN32_FILE_ATTRIBUTE_DATA fileAttributes = { 0 }; + if (!GetFileAttributesExW(file->fullpath, GetFileExInfoStandard, &fileAttributes)) + goto out_fail; + + if (!drive_file_query_from_attributes(file, &fileAttributes, FsInformationClass, output)) + goto out_fail; + + return TRUE; +out_fail: + Stream_Write_UINT32(output, 0); /* Length */ + return FALSE; +} + +BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length, + wStream* input) +{ + INT64 size = 0; + WCHAR* fullpath = NULL; + ULARGE_INTEGER liCreationTime; + ULARGE_INTEGER liLastAccessTime; + ULARGE_INTEGER liLastWriteTime; + ULARGE_INTEGER liChangeTime; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + FILETIME* pftCreationTime = NULL; + FILETIME* pftLastAccessTime = NULL; + FILETIME* pftLastWriteTime = NULL; + UINT32 FileAttributes = 0; + UINT32 FileNameLength = 0; + LARGE_INTEGER liSize; + UINT8 delete_pending = 0; + UINT8 ReplaceIfExists = 0; + DWORD attr = 0; + + if (!file || !input) + return FALSE; + + switch (FsInformationClass) + { + case FileBasicInformation: + if (!Stream_CheckAndLogRequiredLength(TAG, input, 36)) + return FALSE; + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + Stream_Read_UINT64(input, liCreationTime.QuadPart); + Stream_Read_UINT64(input, liLastAccessTime.QuadPart); + Stream_Read_UINT64(input, liLastWriteTime.QuadPart); + Stream_Read_UINT64(input, liChangeTime.QuadPart); + Stream_Read_UINT32(input, FileAttributes); + + if (!PathFileExistsW(file->fullpath)) + return FALSE; + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + WLog_ERR(TAG, "Unable to set file time %s (%" PRId32 ")", file->fullpath, + GetLastError()); + return FALSE; + } + + if (liCreationTime.QuadPart != 0) + { + ftCreationTime.dwHighDateTime = liCreationTime.u.HighPart; + ftCreationTime.dwLowDateTime = liCreationTime.u.LowPart; + pftCreationTime = &ftCreationTime; + } + + if (liLastAccessTime.QuadPart != 0) + { + ftLastAccessTime.dwHighDateTime = liLastAccessTime.u.HighPart; + ftLastAccessTime.dwLowDateTime = liLastAccessTime.u.LowPart; + pftLastAccessTime = &ftLastAccessTime; + } + + if (liLastWriteTime.QuadPart != 0) + { + ftLastWriteTime.dwHighDateTime = liLastWriteTime.u.HighPart; + ftLastWriteTime.dwLowDateTime = liLastWriteTime.u.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + if (liChangeTime.QuadPart != 0 && liChangeTime.QuadPart > liLastWriteTime.QuadPart) + { + ftLastWriteTime.dwHighDateTime = liChangeTime.u.HighPart; + ftLastWriteTime.dwLowDateTime = liChangeTime.u.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + DEBUG_WSTR("SetFileTime %s", file->fullpath); + + SetFileAttributesW(file->fullpath, FileAttributes); + if (!SetFileTime(file->file_handle, pftCreationTime, pftLastAccessTime, + pftLastWriteTime)) + { + WLog_ERR(TAG, "Unable to set file time to %s", file->fullpath); + return FALSE; + } + + break; + + case FileEndOfFileInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232067.aspx */ + case FileAllocationInformation: + if (!Stream_CheckAndLogRequiredLength(TAG, input, 8)) + return FALSE; + + /* http://msdn.microsoft.com/en-us/library/cc232076.aspx */ + Stream_Read_INT64(input, size); + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath, + size, GetLastError()); + return FALSE; + } + + liSize.QuadPart = size; + + if (!SetFilePointerEx(file->file_handle, liSize, NULL, FILE_BEGIN)) + { + WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath, + size, GetLastError()); + return FALSE; + } + + DEBUG_WSTR("Truncate %s", file->fullpath); + + if (SetEndOfFile(file->file_handle) == 0) + { + WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRId32 ")", file->fullpath, + size, GetLastError()); + return FALSE; + } + + break; + + case FileDispositionInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232098.aspx */ + /* http://msdn.microsoft.com/en-us/library/cc241371.aspx */ + if (file->is_dir && !PathIsDirectoryEmptyW(file->fullpath)) + break; /* TODO: SetLastError ??? */ + + if (Length) + { + if (!Stream_CheckAndLogRequiredLength(TAG, input, 1)) + return FALSE; + + Stream_Read_UINT8(input, delete_pending); + } + else + delete_pending = 1; + + if (delete_pending) + { + DEBUG_WSTR("SetDeletePending %s", file->fullpath); + attr = GetFileAttributesW(file->fullpath); + + if (attr & FILE_ATTRIBUTE_READONLY) + { + SetLastError(ERROR_ACCESS_DENIED); + return FALSE; + } + } + + file->delete_pending = delete_pending; + break; + + case FileRenameInformation: + if (!Stream_CheckAndLogRequiredLength(TAG, input, 6)) + return FALSE; + + /* http://msdn.microsoft.com/en-us/library/cc232085.aspx */ + Stream_Read_UINT8(input, ReplaceIfExists); + Stream_Seek_UINT8(input); /* RootDirectory */ + Stream_Read_UINT32(input, FileNameLength); + + if (!Stream_CheckAndLogRequiredLength(TAG, input, FileNameLength)) + return FALSE; + + fullpath = drive_file_combine_fullpath(file->basepath, Stream_ConstPointer(input), + FileNameLength / sizeof(WCHAR)); + + if (!fullpath) + return FALSE; + +#ifdef _WIN32 + + if (file->file_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(file->file_handle); + file->file_handle = INVALID_HANDLE_VALUE; + } + +#endif + DEBUG_WSTR("MoveFileExW %s", file->fullpath); + + if (MoveFileExW(file->fullpath, fullpath, + MOVEFILE_COPY_ALLOWED | + (ReplaceIfExists ? MOVEFILE_REPLACE_EXISTING : 0))) + { + if (!drive_file_set_fullpath(file, fullpath)) + return FALSE; + } + else + { + free(fullpath); + return FALSE; + } + +#ifdef _WIN32 + drive_file_init(file); +#endif + break; + + default: + return FALSE; + } + + return TRUE; +} + +BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery, + const WCHAR* path, UINT32 PathWCharLength, wStream* output) +{ + size_t length = 0; + WCHAR* ent_path = NULL; + + if (!file || !path || !output) + return FALSE; + + if (InitialQuery != 0) + { + /* release search handle */ + if (file->find_handle != INVALID_HANDLE_VALUE) + FindClose(file->find_handle); + + ent_path = drive_file_combine_fullpath(file->basepath, path, PathWCharLength); + /* open new search handle and retrieve the first entry */ + file->find_handle = FindFirstFileW(ent_path, &file->find_data); + free(ent_path); + + if (file->find_handle == INVALID_HANDLE_VALUE) + goto out_fail; + } + else if (!FindNextFileW(file->find_handle, &file->find_data)) + goto out_fail; + + length = _wcslen(file->find_data.cFileName) * 2; + + switch (FsInformationClass) + { + case FileDirectoryInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232097.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 64 + length)) + goto out_fail; + + if (length > UINT32_MAX - 64) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(64 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, + file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32( + output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32( + output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + case FileFullDirectoryInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232068.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 68 + length)) + goto out_fail; + + if (length > UINT32_MAX - 68) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(68 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, + file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32( + output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32( + output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write_UINT32(output, 0); /* EaSize */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + case FileBothDirectoryInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232095.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 93 + length)) + goto out_fail; + + if (length > UINT32_MAX - 93) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(93 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, + file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32( + output, file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32( + output, file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write_UINT32(output, 0); /* EaSize */ + Stream_Write_UINT8(output, 0); /* ShortNameLength */ + /* Reserved(1), MUST NOT be added! */ + Stream_Zero(output, 24); /* ShortName */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + case FileNamesInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232077.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 12 + length)) + goto out_fail; + + if (length > UINT32_MAX - 12) + goto out_fail; + + Stream_Write_UINT32(output, (UINT32)(12 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write(output, file->find_data.cFileName, length); + break; + + default: + WLog_ERR(TAG, "unhandled FsInformationClass %" PRIu32, FsInformationClass); + /* Unhandled FsInformationClass */ + goto out_fail; + } + + return TRUE; +out_fail: + Stream_Write_UINT32(output, 0); /* Length */ + Stream_Write_UINT8(output, 0); /* Padding */ + return FALSE; +} diff --git a/channels/drive/client/drive_file.h b/channels/drive/client/drive_file.h new file mode 100644 index 0000000..761295b --- /dev/null +++ b/channels/drive/client/drive_file.h @@ -0,0 +1,67 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H +#define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H + +#include <winpr/stream.h> +#include <winpr/file.h> +#include <freerdp/channels/log.h> + +#define TAG CHANNELS_TAG("drive.client") + +typedef struct +{ + UINT32 id; + BOOL is_dir; + HANDLE file_handle; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + const WCHAR* basepath; + WCHAR* fullpath; + BOOL delete_pending; + UINT32 FileAttributes; + UINT32 SharedAccess; + UINT32 DesiredAccess; + UINT32 CreateDisposition; + UINT32 CreateOptions; +} DRIVE_FILE; + +DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength, + UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition, + UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess); +BOOL drive_file_free(DRIVE_FILE* file); + +BOOL drive_file_open(DRIVE_FILE* file); +BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset); +BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length); +BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, UINT32 Length); +BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output); +BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length, + wStream* input); +BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery, + const WCHAR* path, UINT32 PathWCharLength, wStream* output); + +#endif /* FREERDP_CHANNEL_DRIVE_FILE_H */ diff --git a/channels/drive/client/drive_main.c b/channels/drive/client/drive_main.c new file mode 100644 index 0000000..0fdc2e0 --- /dev/null +++ b/channels/drive/client/drive_main.c @@ -0,0 +1,1120 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/path.h> +#include <winpr/file.h> +#include <winpr/string.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/stream.h> +#include <winpr/environment.h> +#include <winpr/interlocked.h> +#include <winpr/collections.h> +#include <winpr/shell.h> + +#include <freerdp/freerdp.h> +#include <freerdp/channels/rdpdr.h> + +#include "drive_file.h" + +typedef struct +{ + DEVICE device; + + WCHAR* path; + BOOL automount; + UINT32 PathLength; + wListDictionary* files; + + HANDLE thread; + wMessageQueue* IrpQueue; + + DEVMAN* devman; + + rdpContext* rdpcontext; +} DRIVE_DEVICE; + +static DWORD drive_map_windows_err(DWORD fs_errno) +{ + DWORD rc = 0; + + /* try to return NTSTATUS version of error code */ + + switch (fs_errno) + { + case STATUS_SUCCESS: + rc = STATUS_SUCCESS; + break; + + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + rc = STATUS_ACCESS_DENIED; + break; + + case ERROR_FILE_NOT_FOUND: + rc = STATUS_NO_SUCH_FILE; + break; + + case ERROR_BUSY_DRIVE: + rc = STATUS_DEVICE_BUSY; + break; + + case ERROR_INVALID_DRIVE: + rc = STATUS_NO_SUCH_DEVICE; + break; + + case ERROR_NOT_READY: + rc = STATUS_NO_SUCH_DEVICE; + break; + + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + rc = STATUS_OBJECT_NAME_COLLISION; + break; + + case ERROR_INVALID_NAME: + rc = STATUS_NO_SUCH_FILE; + break; + + case ERROR_INVALID_HANDLE: + rc = STATUS_INVALID_HANDLE; + break; + + case ERROR_NO_MORE_FILES: + rc = STATUS_NO_MORE_FILES; + break; + + case ERROR_DIRECTORY: + rc = STATUS_NOT_A_DIRECTORY; + break; + + case ERROR_PATH_NOT_FOUND: + rc = STATUS_OBJECT_PATH_NOT_FOUND; + break; + + default: + rc = STATUS_UNSUCCESSFUL; + WLog_ERR(TAG, "Error code not found: %" PRIu32 "", fs_errno); + break; + } + + return rc; +} + +static DRIVE_FILE* drive_get_file_by_id(DRIVE_DEVICE* drive, UINT32 id) +{ + DRIVE_FILE* file = NULL; + void* key = (void*)(size_t)id; + + if (!drive) + return NULL; + + file = (DRIVE_FILE*)ListDictionary_GetItemValue(drive->files, key); + return file; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT32 FileId = 0; + DRIVE_FILE* file = NULL; + BYTE Information = 0; + UINT32 FileAttributes = 0; + UINT32 SharedAccess = 0; + UINT32 DesiredAccess = 0; + UINT32 CreateDisposition = 0; + UINT32 CreateOptions = 0; + UINT32 PathLength = 0; + UINT64 allocationSize = 0; + const WCHAR* path = NULL; + + if (!drive || !irp || !irp->devman || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 6 * 4 + 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, DesiredAccess); + Stream_Read_UINT64(irp->input, allocationSize); + Stream_Read_UINT32(irp->input, FileAttributes); + Stream_Read_UINT32(irp->input, SharedAccess); + Stream_Read_UINT32(irp->input, CreateDisposition); + Stream_Read_UINT32(irp->input, CreateOptions); + Stream_Read_UINT32(irp->input, PathLength); + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength)) + return ERROR_INVALID_DATA; + + path = Stream_ConstPointer(irp->input); + FileId = irp->devman->id_sequence++; + file = drive_file_new(drive->path, path, PathLength / sizeof(WCHAR), FileId, DesiredAccess, + CreateDisposition, CreateOptions, FileAttributes, SharedAccess); + + if (!file) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + FileId = 0; + Information = 0; + } + else + { + void* key = (void*)(size_t)file->id; + + if (!ListDictionary_Add(drive->files, key, file)) + { + WLog_ERR(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + + switch (CreateDisposition) + { + case FILE_SUPERSEDE: + case FILE_OPEN: + case FILE_CREATE: + case FILE_OVERWRITE: + Information = FILE_SUPERSEDED; + break; + + case FILE_OPEN_IF: + Information = FILE_OPENED; + break; + + case FILE_OVERWRITE_IF: + Information = FILE_OVERWRITTEN; + break; + + default: + Information = 0; + break; + } + } + + Stream_Write_UINT32(irp->output, FileId); + Stream_Write_UINT8(irp->output, Information); + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_close(DRIVE_DEVICE* drive, IRP* irp) +{ + void* key = NULL; + DRIVE_FILE* file = NULL; + + if (!drive || !irp || !irp->Complete || !irp->output) + return ERROR_INVALID_PARAMETER; + + file = drive_get_file_by_id(drive, irp->FileId); + key = (void*)(size_t)irp->FileId; + + if (!file) + irp->IoStatus = STATUS_UNSUCCESSFUL; + else + { + ListDictionary_Take(drive->files, key); + + if (drive_file_free(file)) + irp->IoStatus = STATUS_SUCCESS; + else + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + Stream_Zero(irp->output, 5); /* Padding(5) */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_read(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = NULL; + UINT32 Length = 0; + UINT64 Offset = 0; + + if (!drive || !irp || !irp->output || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else if (!drive_file_seek(file, Offset)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + + if (!Stream_EnsureRemainingCapacity(irp->output, Length + 4)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INTERNAL_ERROR; + } + else if (Length == 0) + Stream_Write_UINT32(irp->output, 0); + else + { + BYTE* buffer = Stream_PointerAs(irp->output, BYTE) + sizeof(UINT32); + + if (!drive_file_read(file, buffer, &Length)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Stream_Write_UINT32(irp->output, 0); + } + else + { + Stream_Write_UINT32(irp->output, Length); + Stream_Seek(irp->output, Length); + } + } + + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_write(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = NULL; + UINT32 Length = 0; + UINT64 Offset = 0; + + if (!drive || !irp || !irp->input || !irp->output || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + Stream_Seek(irp->input, 20); /* Padding */ + const void* ptr = Stream_ConstPointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else if (!drive_file_seek(file, Offset)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + else if (!drive_file_write(file, ptr, Length)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + + Stream_Write_UINT32(irp->output, Length); + Stream_Write_UINT8(irp->output, 0); /* Padding */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_information(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = NULL; + UINT32 FsInformationClass = 0; + + if (!drive || !irp || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else if (!drive_file_query_information(file, FsInformationClass, irp->output)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_set_information(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = NULL; + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + if (!drive || !irp || !irp->Complete || !irp->input || !irp->output) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Read_UINT32(irp->input, Length); + Stream_Seek(irp->input, 24); /* Padding */ + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else if (!drive_file_set_information(file, FsInformationClass, Length, irp->input)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + if (file && file->is_dir && !PathIsDirectoryEmptyW(file->fullpath)) + irp->IoStatus = STATUS_DIRECTORY_NOT_EMPTY; + + Stream_Write_UINT32(irp->output, Length); + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_volume_information(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT32 FsInformationClass = 0; + wStream* output = NULL; + DWORD lpSectorsPerCluster = 0; + DWORD lpBytesPerSector = 0; + DWORD lpNumberOfFreeClusters = 0; + DWORD lpTotalNumberOfClusters = 0; + WIN32_FILE_ATTRIBUTE_DATA wfad = { 0 }; + WCHAR LabelBuffer[32] = { 0 }; + + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + output = irp->output; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + GetDiskFreeSpaceW(drive->path, &lpSectorsPerCluster, &lpBytesPerSector, &lpNumberOfFreeClusters, + &lpTotalNumberOfClusters); + + switch (FsInformationClass) + { + case FileFsVolumeInformation: + { + /* http://msdn.microsoft.com/en-us/library/cc232108.aspx */ + const WCHAR* volumeLabel = + InitializeConstWCharFromUtf8("FREERDP", LabelBuffer, ARRAYSIZE(LabelBuffer)); + const size_t volumeLabelLen = (_wcslen(volumeLabel) + 1) * sizeof(WCHAR); + const size_t length = 17ul + volumeLabelLen; + + Stream_Write_UINT32(output, length); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + GetFileAttributesExW(drive->path, GetFileExInfoStandard, &wfad); + Stream_Write_UINT32(output, wfad.ftCreationTime.dwLowDateTime); /* VolumeCreationTime */ + Stream_Write_UINT32(output, + wfad.ftCreationTime.dwHighDateTime); /* VolumeCreationTime */ + Stream_Write_UINT32(output, lpNumberOfFreeClusters & 0xffff); /* VolumeSerialNumber */ + Stream_Write_UINT32(output, volumeLabelLen); /* VolumeLabelLength */ + Stream_Write_UINT8(output, 0); /* SupportsObjects */ + /* Reserved(1), MUST NOT be added! */ + Stream_Write(output, volumeLabel, volumeLabelLen); /* VolumeLabel (Unicode) */ + } + break; + + case FileFsSizeInformation: + /* http://msdn.microsoft.com/en-us/library/cc232107.aspx */ + Stream_Write_UINT32(output, 24); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 24)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */ + Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */ + Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */ + Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */ + break; + + case FileFsAttributeInformation: + { + /* http://msdn.microsoft.com/en-us/library/cc232101.aspx */ + const WCHAR* diskType = + InitializeConstWCharFromUtf8("FAT32", LabelBuffer, ARRAYSIZE(LabelBuffer)); + const size_t diskTypeLen = (wcslen(diskType) + 1) * sizeof(WCHAR); + const size_t length = 12ul + diskTypeLen; + Stream_Write_UINT32(output, length); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES | + FILE_UNICODE_ON_DISK); /* FileSystemAttributes */ + Stream_Write_UINT32(output, MAX_PATH); /* MaximumComponentNameLength */ + Stream_Write_UINT32(output, diskTypeLen); /* FileSystemNameLength */ + Stream_Write(output, diskType, diskTypeLen); /* FileSystemName (Unicode) */ + } + break; + + case FileFsFullSizeInformation: + /* http://msdn.microsoft.com/en-us/library/cc232104.aspx */ + Stream_Write_UINT32(output, 32); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 32)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */ + Stream_Write_UINT64(output, + lpNumberOfFreeClusters); /* CallerAvailableAllocationUnits */ + Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */ + Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */ + Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */ + break; + + case FileFsDeviceInformation: + /* http://msdn.microsoft.com/en-us/library/cc232109.aspx */ + Stream_Write_UINT32(output, 8); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 8)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, FILE_DEVICE_DISK); /* DeviceType */ + Stream_Write_UINT32(output, 0); /* Characteristics */ + break; + + default: + irp->IoStatus = STATUS_UNSUCCESSFUL; + Stream_Write_UINT32(output, 0); /* Length */ + break; + } + + return irp->Complete(irp); +} + +/* http://msdn.microsoft.com/en-us/library/cc241518.aspx */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_silent_ignore(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT32 FsInformationClass = 0; + + if (!drive || !irp || !irp->output || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Write_UINT32(irp->output, 0); /* Length */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp) +{ + const WCHAR* path = NULL; + DRIVE_FILE* file = NULL; + BYTE InitialQuery = 0; + UINT32 PathLength = 0; + UINT32 FsInformationClass = 0; + + if (!drive || !irp || !irp->Complete) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Read_UINT8(irp->input, InitialQuery); + Stream_Read_UINT32(irp->input, PathLength); + Stream_Seek(irp->input, 23); /* Padding */ + path = Stream_ConstPointer(irp->input); + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength)) + return ERROR_INVALID_DATA; + + file = drive_get_file_by_id(drive, irp->FileId); + + if (file == NULL) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Stream_Write_UINT32(irp->output, 0); /* Length */ + } + else if (!drive_file_query_directory(file, FsInformationClass, InitialQuery, path, + PathLength / sizeof(WCHAR), irp->output)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_directory_control(DRIVE_DEVICE* drive, IRP* irp) +{ + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + switch (irp->MinorFunction) + { + case IRP_MN_QUERY_DIRECTORY: + return drive_process_irp_query_directory(drive, irp); + + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: /* TODO */ + return irp->Discard(irp); + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + Stream_Write_UINT32(irp->output, 0); /* Length */ + return irp->Complete(irp); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_device_control(DRIVE_DEVICE* drive, IRP* irp) +{ + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */ + return irp->Complete(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT error = 0; + + if (!drive || !irp) + return ERROR_INVALID_PARAMETER; + + irp->IoStatus = STATUS_SUCCESS; + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + error = drive_process_irp_create(drive, irp); + break; + + case IRP_MJ_CLOSE: + error = drive_process_irp_close(drive, irp); + break; + + case IRP_MJ_READ: + error = drive_process_irp_read(drive, irp); + break; + + case IRP_MJ_WRITE: + error = drive_process_irp_write(drive, irp); + break; + + case IRP_MJ_QUERY_INFORMATION: + error = drive_process_irp_query_information(drive, irp); + break; + + case IRP_MJ_SET_INFORMATION: + error = drive_process_irp_set_information(drive, irp); + break; + + case IRP_MJ_QUERY_VOLUME_INFORMATION: + error = drive_process_irp_query_volume_information(drive, irp); + break; + + case IRP_MJ_LOCK_CONTROL: + error = drive_process_irp_silent_ignore(drive, irp); + break; + + case IRP_MJ_DIRECTORY_CONTROL: + error = drive_process_irp_directory_control(drive, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + error = drive_process_irp_device_control(drive, irp); + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + error = irp->Complete(irp); + break; + } + + return error; +} + +static DWORD WINAPI drive_thread_func(LPVOID arg) +{ + IRP* irp = NULL; + wMessage message = { 0 }; + DRIVE_DEVICE* drive = (DRIVE_DEVICE*)arg; + UINT error = CHANNEL_RC_OK; + + if (!drive) + { + error = ERROR_INVALID_PARAMETER; + goto fail; + } + + while (1) + { + if (!MessageQueue_Wait(drive->IrpQueue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(drive->IrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + irp = (IRP*)message.wParam; + + if (irp) + { + if ((error = drive_process_irp(drive, irp))) + { + WLog_ERR(TAG, "drive_process_irp failed with error %" PRIu32 "!", error); + break; + } + } + } + +fail: + + if (error && drive && drive->rdpcontext) + setChannelError(drive->rdpcontext, error, "drive_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_irp_request(DEVICE* device, IRP* irp) +{ + DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + if (!MessageQueue_Post(drive->IrpQueue, NULL, 0, (void*)irp, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT drive_free_int(DRIVE_DEVICE* drive) +{ + UINT error = CHANNEL_RC_OK; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + CloseHandle(drive->thread); + ListDictionary_Free(drive->files); + MessageQueue_Free(drive->IrpQueue); + Stream_Free(drive->device.data, TRUE); + free(drive->path); + free(drive); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_free(DEVICE* device) +{ + DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device; + UINT error = CHANNEL_RC_OK; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + if (MessageQueue_PostQuit(drive->IrpQueue, 0) && + (WaitForSingleObject(drive->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + return drive_free_int(drive); +} + +/** + * Helper function used for freeing list dictionary value object + */ +static void drive_file_objfree(void* obj) +{ + drive_file_free((DRIVE_FILE*)obj); +} + +static void drive_message_free(void* obj) +{ + wMessage* msg = obj; + if (!msg) + return; + if (msg->id != 0) + return; + + IRP* irp = (IRP*)msg->wParam; + if (!irp) + return; + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, const char* name, + const char* path, BOOL automount) +{ + size_t length = 0; + DRIVE_DEVICE* drive = NULL; + UINT error = ERROR_INTERNAL_ERROR; + + if (!pEntryPoints || !name || !path) + { + WLog_ERR(TAG, "[%s] Invalid parameters: pEntryPoints=%p, name=%p, path=%p", pEntryPoints, + name, path); + return ERROR_INVALID_PARAMETER; + } + + if (name[0] && path[0]) + { + size_t pathLength = strnlen(path, MAX_PATH); + drive = (DRIVE_DEVICE*)calloc(1, sizeof(DRIVE_DEVICE)); + + if (!drive) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + drive->device.type = RDPDR_DTYP_FILESYSTEM; + drive->device.IRPRequest = drive_irp_request; + drive->device.Free = drive_free; + drive->rdpcontext = pEntryPoints->rdpcontext; + drive->automount = automount; + length = strlen(name); + drive->device.data = Stream_New(NULL, length + 1); + + if (!drive->device.data) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + for (size_t i = 0; i < length; i++) + { + /* Filter 2.2.1.3 Device Announce Header (DEVICE_ANNOUNCE) forbidden symbols */ + switch (name[i]) + { + case ':': + case '<': + case '>': + case '\"': + case '/': + case '\\': + case '|': + case ' ': + Stream_Write_UINT8(drive->device.data, '_'); + break; + default: + Stream_Write_UINT8(drive->device.data, (BYTE)name[i]); + break; + } + } + Stream_Write_UINT8(drive->device.data, '\0'); + + drive->device.name = (const char*)Stream_Buffer(drive->device.data); + if (!drive->device.name) + goto out_error; + + if ((pathLength > 1) && (path[pathLength - 1] == '/')) + pathLength--; + + drive->path = ConvertUtf8NToWCharAlloc(path, pathLength, NULL); + if (!drive->path) + { + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + drive->files = ListDictionary_New(TRUE); + + if (!drive->files) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + ListDictionary_ValueObject(drive->files)->fnObjectFree = drive_file_objfree; + drive->IrpQueue = MessageQueue_New(NULL); + + if (!drive->IrpQueue) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + wObject* obj = MessageQueue_Object(drive->IrpQueue); + WINPR_ASSERT(obj); + obj->fnObjectFree = drive_message_free; + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE*)drive))) + { + WLog_ERR(TAG, "RegisterDevice failed with error %" PRIu32 "!", error); + goto out_error; + } + + if (!(drive->thread = + CreateThread(NULL, 0, drive_thread_func, drive, CREATE_SUSPENDED, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_error; + } + + ResumeThread(drive->thread); + } + + return CHANNEL_RC_OK; +out_error: + drive_free_int(drive); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT drive_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)) +{ + RDPDR_DRIVE* drive = NULL; + UINT error = 0; +#ifdef WIN32 + int len; + char devlist[512], buf[512]; + char* bufdup; + char* devdup; +#endif + + WINPR_ASSERT(pEntryPoints); + + drive = (RDPDR_DRIVE*)pEntryPoints->device; + WINPR_ASSERT(drive); + +#ifndef WIN32 + if (strcmp(drive->Path, "*") == 0) + { + /* all drives */ + free(drive->Path); + drive->Path = _strdup("/"); + + if (!drive->Path) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + else if (strcmp(drive->Path, "%") == 0) + { + free(drive->Path); + drive->Path = GetKnownPath(KNOWN_PATH_HOME); + + if (!drive->Path) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + error = + drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path, drive->automount); +#else + /* Special case: path[0] == '*' -> export all drives */ + /* Special case: path[0] == '%' -> user home dir */ + if (strcmp(drive->Path, "%") == 0) + { + GetEnvironmentVariableA("USERPROFILE", buf, sizeof(buf)); + PathCchAddBackslashA(buf, sizeof(buf)); + free(drive->Path); + drive->Path = _strdup(buf); + + if (!drive->Path) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path, + drive->automount); + } + else if (strcmp(drive->Path, "*") == 0) + { + /* Enumerate all devices: */ + GetLogicalDriveStringsA(sizeof(devlist) - 1, devlist); + + for (size_t i = 0;; i++) + { + char* dev = &devlist[i * 4]; + if (!*dev) + break; + if (*dev > 'B') + { + /* Suppress disk drives A and B to avoid pesty messages */ + len = sprintf_s(buf, sizeof(buf) - 4, "%s", drive->device.Name); + buf[len] = '_'; + buf[len + 1] = dev[0]; + buf[len + 2] = 0; + buf[len + 3] = 0; + + if (!(bufdup = _strdup(buf))) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(devdup = _strdup(dev))) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = drive_register_drive_path(pEntryPoints, bufdup, devdup, TRUE))) + { + break; + } + } + } + } + else + { + error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path, + drive->automount); + } + +#endif + return error; +} |