mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/win32-vd_agent
synced 2025-12-30 01:30:17 +00:00
Some characters are reserved and should not be used in Windows independently by the file system used. This avoid to use paths in the filename which could lead to some nasty hacks (like names like "..\hack.txt"). The return statement cause the file transfer to be aborted with VD_AGENT_FILE_XFER_STATUS_ERROR as status. ":" is used to separate filenames from stream names and can be used to create hidden streams. Also is used for drive separator (A:) or device names (NUL:). "/" and "\" are reserved for components (directory, filename, drive, share, server) separators. "*" and "?" are wildcards (which on Windows are supported by different APIs too). "<", ">", """ and "|" are reserved for shell usage. More information on "Naming Files, Paths, and Namespaces" page at https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx This fixes also https://bugzilla.redhat.com/show_bug.cgi?id=1520393. Signed-off-by: Frediano Ziglio <fziglio@redhat.com> Acked-by: Christophe Fergeau <cfergeau@redhat.com>
287 lines
8.7 KiB
C++
287 lines
8.7 KiB
C++
/*
|
|
Copyright (C) 2013 Red Hat, Inc.
|
|
|
|
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 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <windows.h>
|
|
#include <shlobj.h>
|
|
#define __STDC_FORMAT_MACROS
|
|
#define __USE_MINGW_ANSI_STDIO 1
|
|
|
|
// compiler specific definitions
|
|
#ifdef _MSC_VER // compiling with Visual Studio
|
|
#define PRIu64 "I64u"
|
|
#else // compiling with mingw
|
|
#include <inttypes.h>
|
|
#endif // compiler specific definitions
|
|
|
|
#include <stdio.h>
|
|
#include <spice/macros.h>
|
|
|
|
#include "file_xfer.h"
|
|
#include "as_user.h"
|
|
|
|
#define FILENAME_RESERVED_CHAR_LIST \
|
|
":" /* streams and devices */ \
|
|
"/\\" /* components separator */ \
|
|
"?*" /* wildcards */ \
|
|
"<>\"|" /* reserved to shell */
|
|
|
|
void FileXfer::reset()
|
|
{
|
|
FileXferTasks::iterator iter;
|
|
FileXferTask* task;
|
|
|
|
for (iter = _tasks.begin(); iter != _tasks.end(); iter++) {
|
|
task = iter->second;
|
|
task->cancel();
|
|
delete task;
|
|
}
|
|
_tasks.clear();
|
|
}
|
|
|
|
FileXfer::~FileXfer()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void FileXfer::handle_start(VDAgentFileXferStartMessage* start,
|
|
VDAgentFileXferStatusMessage* status)
|
|
{
|
|
char* file_meta = (char*)start->data;
|
|
TCHAR file_path[MAX_PATH];
|
|
char file_name[MAX_PATH];
|
|
ULARGE_INTEGER free_bytes;
|
|
FileXferTask* task;
|
|
uint64_t file_size;
|
|
HANDLE handle;
|
|
AsUser as_user;
|
|
int wlen;
|
|
|
|
status->id = start->id;
|
|
status->result = VD_AGENT_FILE_XFER_STATUS_ERROR;
|
|
if (!g_key_get_string(file_meta, "vdagent-file-xfer", "name", file_name, sizeof(file_name)) ||
|
|
!g_key_get_uint64(file_meta, "vdagent-file-xfer", "size", &file_size)) {
|
|
vd_printf("file id %u meta parsing failed", start->id);
|
|
return;
|
|
}
|
|
vd_printf("%u %s (%" PRIu64 ")", start->id, file_name, file_size);
|
|
if (strcspn(file_name, FILENAME_RESERVED_CHAR_LIST) != strlen(file_name)) {
|
|
vd_printf("filename contains invalid characters");
|
|
return;
|
|
}
|
|
if (!as_user.begin()) {
|
|
vd_printf("as_user failed");
|
|
return;
|
|
}
|
|
|
|
if (FAILED(SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY | CSIDL_FLAG_CREATE, NULL,
|
|
SHGFP_TYPE_CURRENT, file_path))) {
|
|
vd_printf("failed getting desktop path");
|
|
return;
|
|
}
|
|
if (!GetDiskFreeSpaceEx(file_path, &free_bytes, NULL, NULL)) {
|
|
vd_printf("failed getting disk free space %lu", GetLastError());
|
|
return;
|
|
}
|
|
if (free_bytes.QuadPart < file_size) {
|
|
vd_printf("insufficient disk space %" PRIu64, free_bytes.QuadPart);
|
|
return;
|
|
}
|
|
|
|
wlen = _tcslen(file_path);
|
|
// make sure we have enough space
|
|
// (1 char for separator, 1 char for filename and 1 char for NUL terminator)
|
|
if (wlen + 3 >= MAX_PATH) {
|
|
vd_printf("error: file too long %ls\\%s", file_path, file_name);
|
|
return;
|
|
}
|
|
|
|
file_path[wlen++] = TEXT('\\');
|
|
file_path[wlen] = TEXT('\0');
|
|
|
|
const int MAX_ATTEMPTS = 64; // matches behavior of linux vdagent
|
|
const size_t POSTFIX_LEN = 6; // up to 2 digits in parentheses and final NULL: " (xx)"
|
|
char *extension = strrchr(file_name, '.');
|
|
if (!extension)
|
|
extension = strchr(file_name, 0);
|
|
|
|
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
char dest_filename[SPICE_N_ELEMENTS(file_name) + POSTFIX_LEN];
|
|
if (attempt == 0) {
|
|
strcpy(dest_filename, file_name);
|
|
} else {
|
|
sprintf_s(dest_filename, SPICE_N_ELEMENTS(dest_filename),
|
|
"%.*s (%d)%s", int(extension - file_name), file_name, attempt, extension);
|
|
}
|
|
if ((MultiByteToWideChar(CP_UTF8, 0, dest_filename, -1, file_path + wlen, MAX_PATH - wlen)) == 0) {
|
|
vd_printf("failed converting file_name:%s to WideChar", dest_filename);
|
|
return;
|
|
}
|
|
handle = CreateFile(file_path, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
break;
|
|
}
|
|
|
|
// If the file already exists, we can re-try with a new filename. If
|
|
// it's a different error, there's not much we can do.
|
|
if (GetLastError() != ERROR_FILE_EXISTS) {
|
|
vd_printf("Failed creating %ls %lu", file_path, GetLastError());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
vd_printf("Failed creating %ls. More than 63 copies exist?", file_path);
|
|
return;
|
|
}
|
|
task = new FileXferTask(handle, file_size, file_path);
|
|
_tasks[start->id] = task;
|
|
status->result = VD_AGENT_FILE_XFER_STATUS_CAN_SEND_DATA;
|
|
}
|
|
|
|
bool FileXfer::handle_data(VDAgentFileXferDataMessage* data,
|
|
VDAgentFileXferStatusMessage* status)
|
|
{
|
|
FileXferTasks::iterator iter;
|
|
FileXferTask* task = NULL;
|
|
DWORD written;
|
|
|
|
status->id = data->id;
|
|
status->result = VD_AGENT_FILE_XFER_STATUS_ERROR;
|
|
iter = _tasks.find(data->id);
|
|
if (iter == _tasks.end()) {
|
|
vd_printf("file id %u not found", data->id);
|
|
goto fin;
|
|
}
|
|
task = iter->second;
|
|
task->pos += data->size;
|
|
if (task->pos > task->size) {
|
|
vd_printf("file xfer is longer than expected");
|
|
goto fin;
|
|
}
|
|
if (!WriteFile(task->handle, data->data, (DWORD)data->size,
|
|
&written, NULL) || written != data->size) {
|
|
vd_printf("file write failed %lu", GetLastError());
|
|
goto fin;
|
|
}
|
|
if (task->pos < task->size) {
|
|
return false;
|
|
}
|
|
vd_printf("%u completed", iter->first);
|
|
status->result = VD_AGENT_FILE_XFER_STATUS_SUCCESS;
|
|
|
|
fin:
|
|
if (task) {
|
|
CloseHandle(task->handle);
|
|
if (status->result != VD_AGENT_FILE_XFER_STATUS_SUCCESS) {
|
|
DeleteFile(task->name);
|
|
}
|
|
_tasks.erase(iter);
|
|
delete task;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FileXferTask::cancel()
|
|
{
|
|
CloseHandle(handle);
|
|
DeleteFile(name);
|
|
}
|
|
|
|
void FileXfer::handle_status(VDAgentFileXferStatusMessage* status)
|
|
{
|
|
FileXferTasks::iterator iter;
|
|
FileXferTask* task;
|
|
|
|
vd_printf("id %u result %u", status->id, status->result);
|
|
if (status->result != VD_AGENT_FILE_XFER_STATUS_CANCELLED) {
|
|
vd_printf("only cancel is permitted");
|
|
return;
|
|
}
|
|
iter = _tasks.find(status->id);
|
|
if (iter == _tasks.end()) {
|
|
vd_printf("file id %u not found", status->id);
|
|
return;
|
|
}
|
|
task = iter->second;
|
|
task->cancel();
|
|
_tasks.erase(iter);
|
|
delete task;
|
|
}
|
|
|
|
bool FileXfer::dispatch(VDAgentMessage* msg, VDAgentFileXferStatusMessage* status)
|
|
{
|
|
bool ret = false;
|
|
|
|
switch (msg->type) {
|
|
case VD_AGENT_FILE_XFER_START:
|
|
handle_start((VDAgentFileXferStartMessage*)msg->data, status);
|
|
ret = true;
|
|
break;
|
|
case VD_AGENT_FILE_XFER_DATA:
|
|
ret = handle_data((VDAgentFileXferDataMessage*)msg->data, status);
|
|
break;
|
|
case VD_AGENT_FILE_XFER_STATUS:
|
|
handle_status((VDAgentFileXferStatusMessage*)msg->data);
|
|
break;
|
|
default:
|
|
vd_printf("unsupported message type %u size %u", msg->type, msg->size);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//minimal parsers for GKeyFile, supporting only key=value with no spaces.
|
|
#define G_KEY_MAX_LEN 256
|
|
|
|
bool FileXfer::g_key_get_string(char* data, const char* group, const char* key, char* value,
|
|
unsigned vsize)
|
|
{
|
|
char group_pfx[G_KEY_MAX_LEN], key_pfx[G_KEY_MAX_LEN];
|
|
char *group_pos, *key_pos, *next_group_pos, *start, *end;
|
|
size_t len;
|
|
|
|
snprintf(group_pfx, sizeof(group_pfx), "[%s]", group);
|
|
if (!(group_pos = strstr((char*)data, group_pfx))) return false;
|
|
|
|
snprintf(key_pfx, sizeof(key_pfx), "\n%s=", key);
|
|
if (!(key_pos = strstr(group_pos, key_pfx))) return false;
|
|
|
|
next_group_pos = strstr(group_pos + strlen(group_pfx), "\n[");
|
|
if (next_group_pos && key_pos > next_group_pos) return false;
|
|
|
|
start = key_pos + strlen(key_pfx);
|
|
end = strchr(start, '\n');
|
|
if (!end) return false;
|
|
|
|
len = end - start;
|
|
if (len >= vsize) return false;
|
|
|
|
memcpy(value, start, len);
|
|
value[len] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileXfer::g_key_get_uint64(char* data, const char* group, const char* key, uint64_t* value)
|
|
{
|
|
char str[G_KEY_MAX_LEN];
|
|
|
|
if (!g_key_get_string(data, group, key, str, sizeof(str)))
|
|
return false;
|
|
return !!sscanf(str, "%" PRIu64, value);
|
|
}
|