[WIP]: [FILES] Implement file transfers.
This commit is contained in:
parent
1a6ee4a438
commit
136bd9a6ab
@ -39,6 +39,9 @@ libguac_client_spice_la_SOURCES = \
|
|||||||
channels/cursor.c \
|
channels/cursor.c \
|
||||||
channels/display.c \
|
channels/display.c \
|
||||||
channels/file.c \
|
channels/file.c \
|
||||||
|
channels/file-download.c \
|
||||||
|
channels/file-ls.c \
|
||||||
|
channels/file-upload.c \
|
||||||
client.c \
|
client.c \
|
||||||
decompose.c \
|
decompose.c \
|
||||||
input.c \
|
input.c \
|
||||||
@ -49,22 +52,25 @@ libguac_client_spice_la_SOURCES = \
|
|||||||
spice.c \
|
spice.c \
|
||||||
user.c
|
user.c
|
||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
argv.h \
|
argv.h \
|
||||||
auth.h \
|
auth.h \
|
||||||
channels/audio.h \
|
channels/audio.h \
|
||||||
channels/clipboard.h \
|
channels/clipboard.h \
|
||||||
channels/cursor.h \
|
channels/cursor.h \
|
||||||
channels/display.h \
|
channels/display.h \
|
||||||
channels/file.h \
|
channels/file.h \
|
||||||
client.h \
|
channels/file-download.h \
|
||||||
decompose.h \
|
channels/file-ls.h \
|
||||||
input.h \
|
channels/file-upload.h \
|
||||||
keyboard.h \
|
client.h \
|
||||||
keymap.h \
|
decompose.h \
|
||||||
log.h \
|
input.h \
|
||||||
settings.h \
|
keyboard.h \
|
||||||
spice.h \
|
keymap.h \
|
||||||
|
log.h \
|
||||||
|
settings.h \
|
||||||
|
spice.h \
|
||||||
user.h
|
user.h
|
||||||
|
|
||||||
libguac_client_spice_la_CFLAGS = \
|
libguac_client_spice_la_CFLAGS = \
|
||||||
|
321
src/protocols/spice/channels/file-download.c
Normal file
321
src/protocols/spice/channels/file-download.c
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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 "common/json.h"
|
||||||
|
#include "file-download.h"
|
||||||
|
#include "file-ls.h"
|
||||||
|
#include "file.h"
|
||||||
|
#include "spice.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/object.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/socket.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/string.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
|
||||||
|
void* guac_spice_file_download_monitor(void* data) {
|
||||||
|
|
||||||
|
guac_spice_folder* folder = (guac_spice_folder*) data;
|
||||||
|
char download_path[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
char download_events[GUAC_SPICE_FOLDER_MAX_EVENTS];
|
||||||
|
char file_path[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
const struct inotify_event *event;
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Starting up file monitor thread.", __func__);
|
||||||
|
|
||||||
|
/* If folder has already been freed, or isn't open, yet, don't do anything. */
|
||||||
|
if (folder == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
download_path[0] = '\0';
|
||||||
|
guac_strlcat(download_path, folder->path, GUAC_SPICE_FOLDER_MAX_PATH);
|
||||||
|
guac_strlcat(download_path, "/Download", GUAC_SPICE_FOLDER_MAX_PATH);
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Watching folder at path \"%s\".", __func__, download_path);
|
||||||
|
|
||||||
|
int notify = inotify_init();
|
||||||
|
|
||||||
|
if (notify == -1) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_ERROR,
|
||||||
|
"%s: Failed to start inotify, automatic downloads will not work: %s",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inotify_add_watch(notify, download_path, IN_CREATE | IN_ATTRIB | IN_CLOSE_WRITE | IN_MOVED_TO | IN_ONLYDIR | IN_EXCL_UNLINK) == -1) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_ERROR,
|
||||||
|
"%s: Failed to set inotify flags for \"%s\".",
|
||||||
|
__func__, download_path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int events = read(notify, download_events, sizeof(download_events));
|
||||||
|
if (events == -1 && errno != EAGAIN) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_ERROR,
|
||||||
|
"%s: Failed to read inotify events: %s",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
|
||||||
|
for (char* ptr = download_events; ptr < download_events + events; ptr += sizeof(struct inotify_event) + event->len) {
|
||||||
|
|
||||||
|
event = (const struct inotify_event *) ptr;
|
||||||
|
|
||||||
|
if (event->mask & IN_ISDIR) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Ignoring event 0x%x for directory %s.", __func__, event->mask, event->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_ERROR,
|
||||||
|
"%s: 0x%x - Downloading the file: %s", __func__, event->mask, event->name, event->cookie);
|
||||||
|
|
||||||
|
file_path[0] = '\0';
|
||||||
|
guac_strlcat(file_path, "/Download/", GUAC_SPICE_FOLDER_MAX_PATH);
|
||||||
|
guac_strlcat(file_path, event->name, GUAC_SPICE_FOLDER_MAX_PATH);
|
||||||
|
// guac_client_for_owner(folder->client, guac_spice_file_download_to_user, file_path);
|
||||||
|
//int fileid = guac_spice_folder_open(folder, file_path, O_WRONLY, 0, 0);
|
||||||
|
// guac_spice_folder_delete(folder, fileid);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_file_download_ack_handler(guac_user* user, guac_stream* stream,
|
||||||
|
char* message, guac_protocol_status status) {
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
guac_spice_file_download_status* download_status = (guac_spice_file_download_status*) stream->data;
|
||||||
|
|
||||||
|
/* Get folder, return error if no folder */
|
||||||
|
guac_spice_folder* folder = spice_client->shared_folder;
|
||||||
|
if (folder == NULL) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If successful, read data */
|
||||||
|
if (status == GUAC_PROTOCOL_STATUS_SUCCESS) {
|
||||||
|
|
||||||
|
/* Attempt read into buffer */
|
||||||
|
char buffer[4096];
|
||||||
|
int bytes_read = guac_spice_folder_read(folder,
|
||||||
|
download_status->file_id,
|
||||||
|
download_status->offset, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
/* If bytes read, send as blob */
|
||||||
|
if (bytes_read > 0) {
|
||||||
|
download_status->offset += bytes_read;
|
||||||
|
guac_protocol_send_blob(user->socket, stream,
|
||||||
|
buffer, bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If EOF, send end */
|
||||||
|
else if (bytes_read == 0) {
|
||||||
|
guac_protocol_send_end(user->socket, stream);
|
||||||
|
guac_user_free_stream(user, stream);
|
||||||
|
free(download_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, fail stream */
|
||||||
|
else {
|
||||||
|
guac_user_log(user, GUAC_LOG_ERROR,
|
||||||
|
"Error reading file for download");
|
||||||
|
guac_protocol_send_end(user->socket, stream);
|
||||||
|
guac_user_free_stream(user, stream);
|
||||||
|
free(download_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, return stream to user */
|
||||||
|
else
|
||||||
|
guac_user_free_stream(user, stream);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_file_download_get_handler(guac_user* user, guac_object* object,
|
||||||
|
char* name) {
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
/* Get folder, ignore request if no folder */
|
||||||
|
guac_spice_folder* folder = spice_client->shared_folder;
|
||||||
|
if (folder == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
flags |= O_RDONLY;
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "%s: folder->path=%s, name=%s", __func__, folder->path, name);
|
||||||
|
|
||||||
|
/* Attempt to open file for reading */
|
||||||
|
int file_id = guac_spice_folder_open(folder, name, flags, 0, 0);
|
||||||
|
if (file_id < 0) {
|
||||||
|
guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"",
|
||||||
|
name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get opened file */
|
||||||
|
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
|
||||||
|
if (file == NULL) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Successful open produced bad file_id: %i",
|
||||||
|
__func__, file_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If directory, send contents of directory */
|
||||||
|
if (S_ISDIR(file->stmode)) {
|
||||||
|
|
||||||
|
/* Create stream data */
|
||||||
|
guac_spice_file_ls_status* ls_status = malloc(sizeof(guac_spice_file_ls_status));
|
||||||
|
ls_status->folder = folder;
|
||||||
|
ls_status->file_id = file_id;
|
||||||
|
guac_strlcpy(ls_status->directory_name, name,
|
||||||
|
sizeof(ls_status->directory_name));
|
||||||
|
|
||||||
|
/* Allocate stream for body */
|
||||||
|
guac_stream* stream = guac_user_alloc_stream(user);
|
||||||
|
stream->ack_handler = guac_spice_file_ls_ack_handler;
|
||||||
|
stream->data = ls_status;
|
||||||
|
|
||||||
|
/* Init JSON object state */
|
||||||
|
guac_common_json_begin_object(user, stream,
|
||||||
|
&ls_status->json_state);
|
||||||
|
|
||||||
|
/* Associate new stream with get request */
|
||||||
|
guac_protocol_send_body(user->socket, object, stream,
|
||||||
|
GUAC_USER_STREAM_INDEX_MIMETYPE, name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, send file contents if downloads are allowed */
|
||||||
|
else if (!folder->disable_download) {
|
||||||
|
|
||||||
|
/* Create stream data */
|
||||||
|
guac_spice_file_download_status* download_status = malloc(sizeof(guac_spice_file_download_status));
|
||||||
|
download_status->file_id = file_id;
|
||||||
|
download_status->offset = 0;
|
||||||
|
|
||||||
|
/* Allocate stream for body */
|
||||||
|
guac_stream* stream = guac_user_alloc_stream(user);
|
||||||
|
stream->data = download_status;
|
||||||
|
stream->ack_handler = guac_spice_file_download_ack_handler;
|
||||||
|
|
||||||
|
/* Associate new stream with get request */
|
||||||
|
guac_protocol_send_body(user->socket, object, stream,
|
||||||
|
"application/octet-stream", name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
guac_client_log(client, GUAC_LOG_INFO, "Unable to download file "
|
||||||
|
"\"%s\", file downloads have been disabled.", name);
|
||||||
|
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* guac_spice_file_download_to_user(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
/* Do not bother attempting the download if the user has left */
|
||||||
|
if (user == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
guac_spice_folder* folder = spice_client->shared_folder;
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
/* Ignore download if folder has been unloaded */
|
||||||
|
if (folder == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Ignore download if downloads have been disabled */
|
||||||
|
if (folder->disable_download) {
|
||||||
|
guac_client_log(client, GUAC_LOG_WARNING, "A download attempt has "
|
||||||
|
"been blocked due to downloads being disabled, however it "
|
||||||
|
"should have been blocked at a higher level. This is likely "
|
||||||
|
"a bug.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt to open requested file */
|
||||||
|
char* path = (char*) data;
|
||||||
|
flags |= O_RDONLY;
|
||||||
|
int file_id = guac_spice_folder_open(folder, path,
|
||||||
|
flags, 0, 0);
|
||||||
|
|
||||||
|
/* If file opened successfully, start stream */
|
||||||
|
if (file_id >= 0) {
|
||||||
|
|
||||||
|
/* Associate stream with transfer status */
|
||||||
|
guac_stream* stream = guac_user_alloc_stream(user);
|
||||||
|
guac_spice_file_download_status* download_status = malloc(sizeof(guac_spice_file_download_status));
|
||||||
|
stream->data = download_status;
|
||||||
|
stream->ack_handler = guac_spice_file_download_ack_handler;
|
||||||
|
download_status->file_id = file_id;
|
||||||
|
download_status->offset = 0;
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Initiating download "
|
||||||
|
"of \"%s\"", __func__, path);
|
||||||
|
|
||||||
|
/* Begin stream */
|
||||||
|
guac_protocol_send_file(user->socket, stream,
|
||||||
|
"application/octet-stream", guac_spice_folder_basename(path));
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
|
||||||
|
/* Download started successfully */
|
||||||
|
return stream;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Download failed */
|
||||||
|
guac_user_log(user, GUAC_LOG_ERROR, "Unable to download \"%s\"", path);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
83
src/protocols/spice/channels/file-download.h
Normal file
83
src/protocols/spice/channels/file-download.h
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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 GUAC_SPICE_FILE_DOWNLOAD_H
|
||||||
|
#define GUAC_SPICE_FILE_DOWNLOAD_H
|
||||||
|
|
||||||
|
#include "common/json.h"
|
||||||
|
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transfer status of a file being downloaded.
|
||||||
|
*/
|
||||||
|
typedef struct guac_spice_file_download_status {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file ID of the file being downloaded.
|
||||||
|
*/
|
||||||
|
int file_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current position within the file.
|
||||||
|
*/
|
||||||
|
uint64_t offset;
|
||||||
|
|
||||||
|
} guac_spice_file_download_status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function which uses Linux's fanotify facility to monitor the "Download"
|
||||||
|
* directory of a shared folder for changes and trigger the automatic download
|
||||||
|
* of that data to the Guacamole user who has access to the shared folder.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to the guac_spice_folder structure in which the Download
|
||||||
|
* folder is located.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL
|
||||||
|
*/
|
||||||
|
void* guac_spice_file_download_monitor(void* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for acknowledgements of receipt of data related to file downloads.
|
||||||
|
*/
|
||||||
|
guac_user_ack_handler guac_spice_file_download_ack_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for get messages. In context of downloads and the filesystem exposed
|
||||||
|
* via the Guacamole protocol, get messages request the body of a file within
|
||||||
|
* the filesystem.
|
||||||
|
*/
|
||||||
|
guac_user_get_handler guac_spice_file_download_get_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for guac_client_for_user() and similar functions which initiates a
|
||||||
|
* file download to a specific user if that user is still connected. The path
|
||||||
|
* for the file to be downloaded must be passed as the arbitrary data parameter
|
||||||
|
* for the function invoking this callback.
|
||||||
|
*/
|
||||||
|
guac_user_callback guac_spice_file_download_to_user;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
127
src/protocols/spice/channels/file-ls.c
Normal file
127
src/protocols/spice/channels/file-ls.c
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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 "file.h"
|
||||||
|
#include "file-ls.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/socket.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
int guac_spice_file_ls_ack_handler(guac_user* user, guac_stream* stream,
|
||||||
|
char* message, guac_protocol_status status) {
|
||||||
|
|
||||||
|
int blob_written = 0;
|
||||||
|
const char* filename;
|
||||||
|
|
||||||
|
guac_spice_file_ls_status* ls_status = (guac_spice_file_ls_status*) stream->data;
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "%s: folder=\"%s\"", __func__, ls_status->folder->path);
|
||||||
|
|
||||||
|
/* If unsuccessful, free stream and abort */
|
||||||
|
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
|
||||||
|
guac_spice_folder_close(ls_status->folder, ls_status->file_id);
|
||||||
|
guac_user_free_stream(user, stream);
|
||||||
|
free(ls_status);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* While directory entries remain */
|
||||||
|
while ((filename = guac_spice_folder_read_dir(ls_status->folder,
|
||||||
|
ls_status->file_id)) != NULL
|
||||||
|
&& !blob_written) {
|
||||||
|
|
||||||
|
char absolute_path[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
|
||||||
|
/* Skip current and parent directory entries */
|
||||||
|
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Concatenate into absolute path - skip if invalid */
|
||||||
|
if (!guac_spice_folder_append_filename(absolute_path,
|
||||||
|
ls_status->directory_name, filename)) {
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG,
|
||||||
|
"Skipping filename \"%s\" - filename is invalid or "
|
||||||
|
"resulting path is too long", filename);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "%s: absolute_path=\"%s\"", __func__, absolute_path);
|
||||||
|
|
||||||
|
/* Attempt to open file to determine type */
|
||||||
|
int flags = (0 | O_RDONLY);
|
||||||
|
int file_id = guac_spice_folder_open(ls_status->folder, absolute_path,
|
||||||
|
flags, 0, 0);
|
||||||
|
if (file_id < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Get opened file */
|
||||||
|
guac_spice_folder_file* file = guac_spice_folder_get_file(ls_status->folder, file_id);
|
||||||
|
if (file == NULL) {
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Successful open produced "
|
||||||
|
"bad file_id: %i", __func__, file_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine mimetype */
|
||||||
|
const char* mimetype;
|
||||||
|
if (S_ISDIR(file->stmode))
|
||||||
|
mimetype = GUAC_USER_STREAM_INDEX_MIMETYPE;
|
||||||
|
else
|
||||||
|
mimetype = "application/octet-stream";
|
||||||
|
|
||||||
|
/* Write entry */
|
||||||
|
blob_written |= guac_common_json_write_property(user, stream,
|
||||||
|
&ls_status->json_state, absolute_path, mimetype);
|
||||||
|
|
||||||
|
guac_spice_folder_close(ls_status->folder, file_id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Complete JSON and cleanup at end of directory */
|
||||||
|
if (filename == NULL) {
|
||||||
|
|
||||||
|
/* Complete JSON object */
|
||||||
|
guac_common_json_end_object(user, stream, &ls_status->json_state);
|
||||||
|
guac_common_json_flush(user, stream, &ls_status->json_state);
|
||||||
|
|
||||||
|
/* Clean up resources */
|
||||||
|
guac_spice_folder_close(ls_status->folder, ls_status->file_id);
|
||||||
|
free(ls_status);
|
||||||
|
|
||||||
|
/* Signal of stream */
|
||||||
|
guac_protocol_send_end(user->socket, stream);
|
||||||
|
guac_user_free_stream(user, stream);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
66
src/protocols/spice/channels/file-ls.h
Normal file
66
src/protocols/spice/channels/file-ls.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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 GUAC_RDP_LS_H
|
||||||
|
#define GUAC_RDP_LS_H
|
||||||
|
|
||||||
|
#include "common/json.h"
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current state of a directory listing operation.
|
||||||
|
*/
|
||||||
|
typedef struct guac_spice_file_ls_status {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filesystem associated with the directory being listed.
|
||||||
|
*/
|
||||||
|
guac_spice_folder* folder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file ID of the directory being listed.
|
||||||
|
*/
|
||||||
|
int file_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The absolute path of the directory being listed.
|
||||||
|
*/
|
||||||
|
char directory_name[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current state of the JSON directory object being written.
|
||||||
|
*/
|
||||||
|
guac_common_json_state json_state;
|
||||||
|
|
||||||
|
} guac_spice_file_ls_status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for ack messages received due to receipt of a "body" or "blob"
|
||||||
|
* instruction associated with a directory list operation.
|
||||||
|
*/
|
||||||
|
guac_user_ack_handler guac_spice_file_ls_ack_handler;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
261
src/protocols/spice/channels/file-upload.c
Normal file
261
src/protocols/spice/channels/file-upload.c
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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 "file.h"
|
||||||
|
#include "spice.h"
|
||||||
|
#include "file-upload.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/object.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/socket.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the given filename to the given upload path, sanitizing the filename
|
||||||
|
* and translating the filename to the root directory.
|
||||||
|
*
|
||||||
|
* @param filename
|
||||||
|
* The filename to sanitize and move to the root directory.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* A pointer to a buffer which should receive the sanitized path. The
|
||||||
|
* buffer must have at least GUAC_RDP_FS_MAX_PATH bytes available.
|
||||||
|
*/
|
||||||
|
static void __generate_upload_path(const char* filename, char* path) {
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Add initial backslash */
|
||||||
|
*(path++) = '\\';
|
||||||
|
|
||||||
|
for (i=1; i<GUAC_SPICE_FOLDER_MAX_PATH; i++) {
|
||||||
|
|
||||||
|
/* Get current, stop at end */
|
||||||
|
char c = *(filename++);
|
||||||
|
if (c == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Replace special characters with underscores */
|
||||||
|
if (c == '/' || c == '\\')
|
||||||
|
c = '_';
|
||||||
|
|
||||||
|
*(path++) = c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminate path */
|
||||||
|
*path = '\0';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_file_upload_file_handler(guac_user* user, guac_stream* stream,
|
||||||
|
char* mimetype, char* filename) {
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
|
||||||
|
int file_id;
|
||||||
|
char file_path[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
|
||||||
|
/* Get filesystem, return error if no filesystem */
|
||||||
|
guac_spice_folder* folder = spice_client->shared_folder;
|
||||||
|
if (folder == NULL) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore upload if uploads have been disabled */
|
||||||
|
if (folder->disable_upload) {
|
||||||
|
guac_client_log(client, GUAC_LOG_WARNING, "A upload attempt has "
|
||||||
|
"been blocked due to uploads being disabled, however it "
|
||||||
|
"should have been blocked at a higher level. This is likely "
|
||||||
|
"a bug.");
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (UPLOAD DISABLED)",
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Translate name */
|
||||||
|
__generate_upload_path(filename, file_path);
|
||||||
|
|
||||||
|
/* Open file */
|
||||||
|
file_id = guac_spice_folder_open(folder, file_path, (O_WRONLY | O_CREAT | O_TRUNC),
|
||||||
|
1, 0);
|
||||||
|
if (file_id < 0) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)",
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init upload status */
|
||||||
|
guac_spice_file_upload_status* upload_status = malloc(sizeof(guac_spice_file_upload_status));
|
||||||
|
upload_status->offset = 0;
|
||||||
|
upload_status->file_id = file_id;
|
||||||
|
stream->data = upload_status;
|
||||||
|
stream->blob_handler = guac_spice_file_upload_blob_handler;
|
||||||
|
stream->end_handler = guac_spice_file_upload_end_handler;
|
||||||
|
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_file_upload_blob_handler(guac_user* user, guac_stream* stream,
|
||||||
|
void* data, int length) {
|
||||||
|
|
||||||
|
int bytes_written;
|
||||||
|
guac_spice_file_upload_status* upload_status = (guac_spice_file_upload_status*) stream->data;
|
||||||
|
|
||||||
|
/* Get filesystem, return error if no filesystem */
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
guac_spice_folder* folder = spice_client->shared_folder;
|
||||||
|
if (folder == NULL) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write entire block */
|
||||||
|
while (length > 0) {
|
||||||
|
|
||||||
|
/* Attempt write */
|
||||||
|
bytes_written = guac_spice_folder_write(folder, upload_status->file_id,
|
||||||
|
upload_status->offset, data, length);
|
||||||
|
|
||||||
|
/* On error, abort */
|
||||||
|
if (bytes_written < 0) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream,
|
||||||
|
"FAIL (BAD WRITE)",
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update counters */
|
||||||
|
upload_status->offset += bytes_written;
|
||||||
|
data = (char *)data + bytes_written;
|
||||||
|
length -= bytes_written;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_file_upload_end_handler(guac_user* user, guac_stream* stream) {
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
guac_spice_file_upload_status* upload_status = (guac_spice_file_upload_status*) stream->data;
|
||||||
|
|
||||||
|
/* Get folder, return error if no filesystem */
|
||||||
|
guac_spice_folder* folder = spice_client->shared_folder;
|
||||||
|
if (folder == NULL) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close file */
|
||||||
|
guac_spice_folder_close(folder, upload_status->file_id);
|
||||||
|
|
||||||
|
/* Acknowledge stream end */
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "OK (STREAM END)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
|
||||||
|
free(upload_status);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_file_upload_put_handler(guac_user* user, guac_object* object,
|
||||||
|
guac_stream* stream, char* mimetype, char* name) {
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
|
|
||||||
|
/* Get folder, return error if no filesystem */
|
||||||
|
guac_spice_folder* folder = spice_client->shared_folder;
|
||||||
|
if (folder == NULL) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore upload if uploads have been disabled */
|
||||||
|
if (folder->disable_upload) {
|
||||||
|
guac_client_log(client, GUAC_LOG_WARNING, "A upload attempt has "
|
||||||
|
"been blocked due to uploads being disabled, however it "
|
||||||
|
"should have been blocked at a higher level. This is likely "
|
||||||
|
"a bug.");
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (UPLOAD DISABLED)",
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open file */
|
||||||
|
int file_id = guac_spice_folder_open(folder, name, (O_WRONLY | O_CREAT | O_TRUNC),
|
||||||
|
1, 0);
|
||||||
|
|
||||||
|
/* Abort on failure */
|
||||||
|
if (file_id < 0) {
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)",
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init upload stream data */
|
||||||
|
guac_spice_file_upload_status* upload_status = malloc(sizeof(guac_spice_file_upload_status));
|
||||||
|
upload_status->offset = 0;
|
||||||
|
upload_status->file_id = file_id;
|
||||||
|
|
||||||
|
/* Allocate stream, init for file upload */
|
||||||
|
stream->data = upload_status;
|
||||||
|
stream->blob_handler = guac_spice_file_upload_blob_handler;
|
||||||
|
stream->end_handler = guac_spice_file_upload_end_handler;
|
||||||
|
|
||||||
|
/* Acknowledge stream creation */
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)",
|
||||||
|
GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
72
src/protocols/spice/channels/file-upload.h
Normal file
72
src/protocols/spice/channels/file-upload.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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 GUAC_SPICE_FILE_UPLOAD_H
|
||||||
|
#define GUAC_SPICE_FILE_UPLOAD_H
|
||||||
|
|
||||||
|
#include "common/json.h"
|
||||||
|
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure which represents the current state of an upload.
|
||||||
|
*/
|
||||||
|
typedef struct guac_spice_file_upload_status {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overall offset within the file that the next write should
|
||||||
|
* occur at.
|
||||||
|
*/
|
||||||
|
uint64_t offset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the file being written to.
|
||||||
|
*/
|
||||||
|
int file_id;
|
||||||
|
|
||||||
|
} guac_spice_file_upload_status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for inbound files related to file uploads.
|
||||||
|
*/
|
||||||
|
guac_user_file_handler guac_spice_file_upload_file_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for stream data related to file uploads.
|
||||||
|
*/
|
||||||
|
guac_user_blob_handler guac_spice_file_upload_blob_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for end-of-stream related to file uploads.
|
||||||
|
*/
|
||||||
|
guac_user_end_handler guac_spice_file_upload_end_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for put messages. In context of uploads and the filesystem exposed
|
||||||
|
* via the Guacamole protocol, put messages request write access to a file
|
||||||
|
* within the filesystem.
|
||||||
|
*/
|
||||||
|
guac_user_put_handler guac_spice_file_upload_put_handler;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -20,8 +20,676 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
#include "file-download.h"
|
||||||
|
#include "file-ls.h"
|
||||||
|
#include "file-upload.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/socket.h>
|
||||||
|
#include <guacamole/string.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates an absolute path for a shared folder to an absolute path which is
|
||||||
|
* within the real "shared folder" path specified in the connection settings.
|
||||||
|
* No checking is performed on the path provided, which is assumed to have
|
||||||
|
* already been normalized and validated as absolute.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The folder containing the file whose path is being translated.
|
||||||
|
*
|
||||||
|
* @param virtual_path
|
||||||
|
* The absolute path to the file on the simulated folder, relative to the
|
||||||
|
* shared folder root.
|
||||||
|
*
|
||||||
|
* @param real_path
|
||||||
|
* The buffer in which to store the absolute path to the real file on the
|
||||||
|
* local filesystem.
|
||||||
|
*/
|
||||||
|
static void __guac_spice_folder_translate_path(guac_spice_folder* folder,
|
||||||
|
const char* virtual_path, char* real_path) {
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: virtual_path=\"%s\", drive_path=\"%s\"", __func__, virtual_path, folder->path);
|
||||||
|
|
||||||
|
/* Get drive path */
|
||||||
|
char* path = folder->path;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Start with path from settings */
|
||||||
|
for (i=0; i<GUAC_SPICE_FOLDER_MAX_PATH-1; i++) {
|
||||||
|
|
||||||
|
/* Break on end-of-string */
|
||||||
|
char c = *(path++);
|
||||||
|
if (c == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Copy character */
|
||||||
|
*(real_path++) = c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Translate path */
|
||||||
|
for (; i<GUAC_SPICE_FOLDER_MAX_PATH-1; i++) {
|
||||||
|
|
||||||
|
/* Stop at end of string */
|
||||||
|
char c = *(virtual_path++);
|
||||||
|
if (c == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Translate backslashes to forward slashes */
|
||||||
|
if (c == '\\')
|
||||||
|
c = '/';
|
||||||
|
|
||||||
|
/* Store in real path buffer */
|
||||||
|
*(real_path++)= c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Null terminator */
|
||||||
|
*real_path = 0;
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: virtual_path=\"%s\", real_path=\"%s\"", __func__, virtual_path, real_path);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_spice_folder* guac_spice_folder_alloc(guac_client* client, const char* folder_path,
|
||||||
|
int create_folder, int disable_download, int disable_upload) {
|
||||||
|
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Initializing shared folder at "
|
||||||
|
"\"%s\".", folder_path);
|
||||||
|
|
||||||
|
/* Create folder if it does not exist */
|
||||||
|
if (create_folder) {
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Creating folder \"%s\" if necessary.",
|
||||||
|
__func__, folder_path);
|
||||||
|
|
||||||
|
/* Log error if directory creation fails */
|
||||||
|
if (mkdir(folder_path, S_IRWXU) && errno != EEXIST) {
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR,
|
||||||
|
"Unable to create folder \"%s\": %s",
|
||||||
|
folder_path, strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_spice_folder* folder = malloc(sizeof(guac_spice_folder));
|
||||||
|
|
||||||
|
folder->client = client;
|
||||||
|
folder->path = strdup(folder_path);
|
||||||
|
folder->file_id_pool = guac_pool_alloc(0);
|
||||||
|
folder->open_files = 0;
|
||||||
|
folder->disable_download = disable_download;
|
||||||
|
folder->disable_upload = disable_upload;
|
||||||
|
|
||||||
|
/* Set up Download directory and watch it. */
|
||||||
|
if (!disable_download) {
|
||||||
|
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "%s: Setting up Download/ folder watch.", __func__);
|
||||||
|
|
||||||
|
if (create_folder) {
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "%s: Creating Download/ folder.",
|
||||||
|
__func__);
|
||||||
|
|
||||||
|
char *download_path;
|
||||||
|
download_path = guac_strdup(folder_path);
|
||||||
|
|
||||||
|
guac_strlcat(download_path, "/Download", GUAC_SPICE_FOLDER_MAX_PATH);
|
||||||
|
|
||||||
|
if (mkdir(folder_path, S_IRWXU) && errno != EEXIST) {
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR,
|
||||||
|
"%s: Unable to create folder \"%s\": %s", __func__,
|
||||||
|
download_path, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pthread_create(&(folder->download_thread), NULL, guac_spice_file_download_monitor, (void*) folder)) {
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR,
|
||||||
|
"%s: Unable to create Download folder thread monitor.", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return folder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guac_spice_folder_free(guac_spice_folder* folder) {
|
||||||
|
guac_pool_free(folder->file_id_pool);
|
||||||
|
free(folder->path);
|
||||||
|
free(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_object* guac_spice_folder_alloc_object(guac_spice_folder *folder, guac_user* user) {
|
||||||
|
|
||||||
|
/* Init folder */
|
||||||
|
guac_object* folder_object = guac_user_alloc_object(user);
|
||||||
|
folder_object->get_handler = guac_spice_file_download_get_handler;
|
||||||
|
|
||||||
|
/* Assign upload handler only if uploads are not disabled. */
|
||||||
|
if (!folder->disable_upload)
|
||||||
|
folder_object->put_handler = guac_spice_file_upload_put_handler;
|
||||||
|
|
||||||
|
folder_object->data = folder;
|
||||||
|
|
||||||
|
/* Send filesystem to user */
|
||||||
|
guac_protocol_send_filesystem(user->socket, folder_object, "Shared Folder");
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
|
||||||
|
return folder_object;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_folder_append_filename(char* fullpath, const char* path,
|
||||||
|
const char* filename) {
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Disallow "." as a filename */
|
||||||
|
if (strcmp(filename, ".") == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Disallow ".." as a filename */
|
||||||
|
if (strcmp(filename, "..") == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Copy path, append trailing slash */
|
||||||
|
for (i=0; i<GUAC_SPICE_FOLDER_MAX_PATH; i++) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Append trailing slash only if:
|
||||||
|
* 1) Trailing slash is not already present
|
||||||
|
* 2) Path is non-empty
|
||||||
|
*/
|
||||||
|
|
||||||
|
char c = path[i];
|
||||||
|
if (c == '\0') {
|
||||||
|
if (i > 0 && path[i-1] != '/' && path[i-1] != '\\')
|
||||||
|
fullpath[i++] = '/';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy character if not end of string */
|
||||||
|
fullpath[i] = c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Append filename */
|
||||||
|
for (; i<GUAC_SPICE_FOLDER_MAX_PATH; i++) {
|
||||||
|
|
||||||
|
char c = *(filename++);
|
||||||
|
if (c == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Filenames may not contain slashes */
|
||||||
|
if (c == '\\' || c == '/')
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Append each character within filename */
|
||||||
|
fullpath[i] = c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verify path length is within maximum */
|
||||||
|
if (i == GUAC_SPICE_FOLDER_MAX_PATH)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Terminate path string */
|
||||||
|
fullpath[i] = '\0';
|
||||||
|
|
||||||
|
/* Append was successful */
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* guac_spice_folder_basename(const char* path) {
|
||||||
|
|
||||||
|
for (const char* c = path; *c != '\0'; c++) {
|
||||||
|
|
||||||
|
/* Reset beginning of path if a path separator is found */
|
||||||
|
if (*c == '/' || *c == '\\')
|
||||||
|
path = c + 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* path now points to the first character after the last path separator */
|
||||||
|
return path;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guac_spice_folder_close(guac_spice_folder* folder, int file_id) {
|
||||||
|
|
||||||
|
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
|
||||||
|
if (file == NULL) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Ignoring close for bad file_id: %i",
|
||||||
|
__func__, file_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = &(folder->files[file_id]);
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Closed \"%s\" (file_id=%i)",
|
||||||
|
__func__, file->absolute_path, file_id);
|
||||||
|
|
||||||
|
/* Close directory, if open */
|
||||||
|
if (file->dir != NULL)
|
||||||
|
closedir(file->dir);
|
||||||
|
|
||||||
|
/* Close file */
|
||||||
|
close(file->fd);
|
||||||
|
|
||||||
|
/* Free paths */
|
||||||
|
free(file->absolute_path);
|
||||||
|
free(file->real_path);
|
||||||
|
|
||||||
|
/* Free ID back to pool */
|
||||||
|
guac_pool_free_int(folder->file_id_pool, file_id);
|
||||||
|
folder->open_files--;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_folder_delete(guac_spice_folder* folder, int file_id) {
|
||||||
|
|
||||||
|
/* Get file */
|
||||||
|
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
|
||||||
|
if (file == NULL) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Delete of bad file_id: %i", __func__, file_id);
|
||||||
|
return GUAC_SPICE_FOLDER_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If directory, attempt removal */
|
||||||
|
if (S_ISDIR(file->stmode)) {
|
||||||
|
if (rmdir(file->real_path)) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: rmdir() failed: \"%s\"", __func__, file->real_path);
|
||||||
|
return guac_spice_folder_get_errorcode(errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, attempt deletion */
|
||||||
|
else if (unlink(file->real_path)) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: unlink() failed: \"%s\"", __func__, file->real_path);
|
||||||
|
return guac_spice_folder_get_errorcode(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void* guac_spice_folder_expose(guac_user* user, void* data) {
|
||||||
|
|
||||||
|
guac_spice_folder* folder = (guac_spice_folder*) data;
|
||||||
|
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Exposing folder \"%s\" to user.", __func__, folder->path);
|
||||||
|
|
||||||
|
/* No need to expose if there is no folder or the user has left */
|
||||||
|
if (user == NULL || folder == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Allocate and expose folder object for user */
|
||||||
|
return guac_spice_folder_alloc_object(folder, user);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_folder_get_errorcode(int err) {
|
||||||
|
|
||||||
|
/* Translate errno codes to GUAC_SPICE_FOLDER codes */
|
||||||
|
switch(err) {
|
||||||
|
case ENFILE:
|
||||||
|
return GUAC_SPICE_FOLDER_ENFILE;
|
||||||
|
|
||||||
|
case ENOENT:
|
||||||
|
return GUAC_SPICE_FOLDER_ENOENT;
|
||||||
|
|
||||||
|
case ENOTDIR:
|
||||||
|
return GUAC_SPICE_FOLDER_ENOTDIR;
|
||||||
|
|
||||||
|
case ENOSPC:
|
||||||
|
return GUAC_SPICE_FOLDER_ENOSPC;
|
||||||
|
|
||||||
|
case EISDIR:
|
||||||
|
return GUAC_SPICE_FOLDER_EISDIR;
|
||||||
|
|
||||||
|
case EACCES:
|
||||||
|
return GUAC_SPICE_FOLDER_EACCES;
|
||||||
|
|
||||||
|
case EEXIST:
|
||||||
|
return GUAC_SPICE_FOLDER_EEXIST;
|
||||||
|
|
||||||
|
case EINVAL:
|
||||||
|
return GUAC_SPICE_FOLDER_EINVAL;
|
||||||
|
|
||||||
|
case ENOSYS:
|
||||||
|
return GUAC_SPICE_FOLDER_ENOSYS;
|
||||||
|
|
||||||
|
case ENOTSUP:
|
||||||
|
return GUAC_SPICE_FOLDER_ENOTSUP;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return GUAC_SPICE_FOLDER_EINVAL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_spice_folder_file* guac_spice_folder_get_file(guac_spice_folder* folder,
|
||||||
|
int file_id) {
|
||||||
|
|
||||||
|
/* Validate ID */
|
||||||
|
if (file_id < 0 || file_id >= GUAC_SPICE_FOLDER_MAX_FILES)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Return file at given ID */
|
||||||
|
return &(folder->files[file_id]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_folder_normalize_path(const char* path, char* abs_path) {
|
||||||
|
|
||||||
|
int path_depth = 0;
|
||||||
|
const char* path_components[GUAC_SPICE_FOLDER_MAX_PATH_DEPTH];
|
||||||
|
|
||||||
|
/* If original path is not absolute, normalization fails */
|
||||||
|
if (path[0] != '/')
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Create scratch copy of path excluding leading slash (we will be
|
||||||
|
* replacing path separators with null terminators and referencing those
|
||||||
|
* substrings directly as path components) */
|
||||||
|
char path_scratch[GUAC_SPICE_FOLDER_MAX_PATH - 1];
|
||||||
|
int length = guac_strlcpy(path_scratch, path + 1,
|
||||||
|
sizeof(path_scratch));
|
||||||
|
|
||||||
|
/* Fail if provided path is too long */
|
||||||
|
if (length >= sizeof(path_scratch))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Locate all path components within path */
|
||||||
|
const char* current_path_component = &(path_scratch[0]);
|
||||||
|
for (int i = 0; i <= length; i++) {
|
||||||
|
|
||||||
|
/* If current character is a path separator, parse as component */
|
||||||
|
char c = path_scratch[i];
|
||||||
|
if (c == '/' || c == '\0') {
|
||||||
|
|
||||||
|
/* Terminate current component */
|
||||||
|
path_scratch[i] = '\0';
|
||||||
|
|
||||||
|
/* If component refers to parent, just move up in depth */
|
||||||
|
if (strcmp(current_path_component, "..") == 0) {
|
||||||
|
if (path_depth > 0)
|
||||||
|
path_depth--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, if component not current directory, add to list */
|
||||||
|
else if (strcmp(current_path_component, ".") != 0
|
||||||
|
&& strcmp(current_path_component, "") != 0) {
|
||||||
|
|
||||||
|
/* Fail normalization if path is too deep */
|
||||||
|
if (path_depth >= GUAC_SPICE_FOLDER_MAX_PATH_DEPTH)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
path_components[path_depth++] = current_path_component;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update start of next component */
|
||||||
|
current_path_component = &(path_scratch[i+1]);
|
||||||
|
|
||||||
|
} /* end if separator */
|
||||||
|
|
||||||
|
/* We do not currently support named streams */
|
||||||
|
else if (c == ':')
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
} /* end for each character */
|
||||||
|
|
||||||
|
/* Add leading slash for resulting absolute path */
|
||||||
|
abs_path[0] = '/';
|
||||||
|
|
||||||
|
/* Append normalized components to path, separated by slashes */
|
||||||
|
guac_strljoin(abs_path + 1, path_components, path_depth,
|
||||||
|
"/", GUAC_SPICE_FOLDER_MAX_PATH - 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_folder_open(guac_spice_folder* folder, const char* path,
|
||||||
|
int flags, bool overwrite, bool directory) {
|
||||||
|
|
||||||
|
char real_path[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
char normalized_path[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
|
||||||
|
struct stat file_stat;
|
||||||
|
int fd;
|
||||||
|
int file_id;
|
||||||
|
guac_spice_folder_file* file;
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: path=\"%s\", flags=0x%x, overwrite=0x%x, "
|
||||||
|
"directory=0x%x", __func__, path, flags, overwrite, directory);
|
||||||
|
|
||||||
|
/* If no files available, return too many open */
|
||||||
|
if (folder->open_files >= GUAC_SPICE_FOLDER_MAX_FILES) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Too many open files.",
|
||||||
|
__func__, path);
|
||||||
|
return GUAC_SPICE_FOLDER_ENFILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If path empty, return an error */
|
||||||
|
if (path[0] == '\0')
|
||||||
|
return GUAC_SPICE_FOLDER_EINVAL;
|
||||||
|
|
||||||
|
/* If path is relative, the file does not exist */
|
||||||
|
else if (path[0] != '\\' && path[0] != '/') {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Access denied - supplied path \"%s\" is relative.",
|
||||||
|
__func__, path);
|
||||||
|
return GUAC_SPICE_FOLDER_ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Translate access into flags */
|
||||||
|
if (directory)
|
||||||
|
flags |= O_DIRECTORY;
|
||||||
|
|
||||||
|
else if (overwrite)
|
||||||
|
flags |= O_TRUNC;
|
||||||
|
|
||||||
|
/* Normalize path, return no-such-file if invalid */
|
||||||
|
if (guac_spice_folder_normalize_path(path, normalized_path)) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Normalization of path \"%s\" failed.", __func__, path);
|
||||||
|
return GUAC_SPICE_FOLDER_ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Normalized path \"%s\" to \"%s\".",
|
||||||
|
__func__, path, normalized_path);
|
||||||
|
|
||||||
|
/* Translate normalized path to real path */
|
||||||
|
__guac_spice_folder_translate_path(folder, normalized_path, real_path);
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Translated path \"%s\" to \"%s\".",
|
||||||
|
__func__, normalized_path, real_path);
|
||||||
|
|
||||||
|
/* Create directory first, if necessary */
|
||||||
|
if (directory && (flags & O_CREAT)) {
|
||||||
|
|
||||||
|
/* Create directory */
|
||||||
|
if (mkdir(real_path, S_IRWXU)) {
|
||||||
|
if (errno != EEXIST || (flags & O_EXCL)) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: mkdir() failed: %s",
|
||||||
|
__func__, strerror(errno));
|
||||||
|
return guac_spice_folder_get_errorcode(errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unset O_CREAT and O_EXCL as directory must exist before open() */
|
||||||
|
flags &= ~(O_CREAT | O_EXCL);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: native open: real_path=\"%s\", flags=0x%x",
|
||||||
|
__func__, real_path, flags);
|
||||||
|
|
||||||
|
/* Open file */
|
||||||
|
fd = open(real_path, flags, S_IRUSR | S_IWUSR);
|
||||||
|
|
||||||
|
/* If file open failed as we're trying to write a dir, retry as read-only */
|
||||||
|
if (fd == -1 && errno == EISDIR) {
|
||||||
|
flags &= ~(O_WRONLY | O_RDWR);
|
||||||
|
flags |= O_RDONLY;
|
||||||
|
fd = open(real_path, flags, S_IRUSR | S_IWUSR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fd == -1) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: open() failed: %s", __func__, strerror(errno));
|
||||||
|
return guac_spice_folder_get_errorcode(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get file ID, init file */
|
||||||
|
file_id = guac_pool_next_int(folder->file_id_pool);
|
||||||
|
file = &(folder->files[file_id]);
|
||||||
|
file->id = file_id;
|
||||||
|
file->fd = fd;
|
||||||
|
file->dir = NULL;
|
||||||
|
file->dir_pattern[0] = '\0';
|
||||||
|
file->absolute_path = strdup(normalized_path);
|
||||||
|
file->real_path = strdup(real_path);
|
||||||
|
file->bytes_written = 0;
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Opened \"%s\" as file_id=%i",
|
||||||
|
__func__, normalized_path, file_id);
|
||||||
|
|
||||||
|
/* Attempt to pull file information */
|
||||||
|
if (fstat(fd, &file_stat) == 0) {
|
||||||
|
|
||||||
|
/* Load size and times */
|
||||||
|
file->size = file_stat.st_size;
|
||||||
|
file->ctime = file_stat.st_ctime;
|
||||||
|
file->mtime = file_stat.st_mtime;
|
||||||
|
file->atime = file_stat.st_atime;
|
||||||
|
file->stmode = file_stat.st_mode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If information cannot be retrieved, fake it */
|
||||||
|
else {
|
||||||
|
|
||||||
|
/* Init information to 0, lacking any alternative */
|
||||||
|
file->size = 0;
|
||||||
|
file->ctime = 0;
|
||||||
|
file->mtime = 0;
|
||||||
|
file->atime = 0;
|
||||||
|
file->stmode = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
folder->open_files++;
|
||||||
|
|
||||||
|
return file_id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_folder_read(guac_spice_folder* folder, int file_id, uint64_t offset,
|
||||||
|
void* buffer, int length) {
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Attempt to read from file: %s", __func__, folder->path);
|
||||||
|
|
||||||
|
int bytes_read;
|
||||||
|
|
||||||
|
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
|
||||||
|
if (file == NULL) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Read from bad file_id: %i", __func__, file_id);
|
||||||
|
return GUAC_SPICE_FOLDER_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt read */
|
||||||
|
lseek(file->fd, offset, SEEK_SET);
|
||||||
|
bytes_read = read(file->fd, buffer, length);
|
||||||
|
|
||||||
|
/* Translate errno on error */
|
||||||
|
if (bytes_read < 0)
|
||||||
|
return guac_spice_folder_get_errorcode(errno);
|
||||||
|
|
||||||
|
return bytes_read;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* guac_spice_folder_read_dir(guac_spice_folder* folder, int file_id) {
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Attempt to read directory: %s", __func__, folder->path);
|
||||||
|
|
||||||
|
guac_spice_folder_file* file;
|
||||||
|
|
||||||
|
struct dirent* result;
|
||||||
|
|
||||||
|
/* Only read if file ID is valid */
|
||||||
|
if (file_id < 0 || file_id >= GUAC_SPICE_FOLDER_MAX_FILES)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
file = &(folder->files[file_id]);
|
||||||
|
|
||||||
|
/* Open directory if not yet open, stop if error */
|
||||||
|
if (file->dir == NULL) {
|
||||||
|
file->dir = fdopendir(file->fd);
|
||||||
|
if (file->dir == NULL)
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read next entry, stop if error or no more entries */
|
||||||
|
if ((result = readdir(file->dir)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Return filename */
|
||||||
|
return result->d_name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_spice_folder_write(guac_spice_folder* folder, int file_id, uint64_t offset,
|
||||||
|
void* buffer, int length) {
|
||||||
|
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Attempt to write file: %s", __func__, folder->path);
|
||||||
|
|
||||||
|
int bytes_written;
|
||||||
|
|
||||||
|
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
|
||||||
|
if (file == NULL) {
|
||||||
|
guac_client_log(folder->client, GUAC_LOG_DEBUG,
|
||||||
|
"%s: Write to bad file_id: %i", __func__, file_id);
|
||||||
|
return GUAC_SPICE_FOLDER_EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt write */
|
||||||
|
lseek(file->fd, offset, SEEK_SET);
|
||||||
|
bytes_written = write(file->fd, buffer, length);
|
||||||
|
|
||||||
|
/* Translate errno on error */
|
||||||
|
if (bytes_written < 0)
|
||||||
|
return guac_spice_folder_get_errorcode(errno);
|
||||||
|
|
||||||
|
file->bytes_written += bytes_written;
|
||||||
|
return bytes_written;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void guac_spice_client_file_transfer_handler(SpiceMainChannel* main_channel,
|
void guac_spice_client_file_transfer_handler(SpiceMainChannel* main_channel,
|
||||||
SpiceFileTransferTask* task, guac_client* client) {
|
SpiceFileTransferTask* task, guac_client* client) {
|
||||||
|
@ -23,10 +23,424 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include "spice-constants.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/object.h>
|
||||||
|
#include <guacamole/pool.h>
|
||||||
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <spice-client-glib-2.0/spice-client.h>
|
#include <spice-client-glib-2.0/spice-client.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An arbitrary file on the shared folder.
|
||||||
|
*/
|
||||||
|
typedef struct guac_spice_folder_file {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of this file.
|
||||||
|
*/
|
||||||
|
int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The absolute path, including filename, of this file on the simulated filesystem.
|
||||||
|
*/
|
||||||
|
char* absolute_path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The real path, including filename, of this file on the local filesystem.
|
||||||
|
*/
|
||||||
|
char* real_path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associated local file descriptor.
|
||||||
|
*/
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associated directory stream, if any. This field only applies
|
||||||
|
* if the file is being used as a directory.
|
||||||
|
*/
|
||||||
|
DIR* dir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pattern the check directory contents against, if any.
|
||||||
|
*/
|
||||||
|
char dir_pattern[GUAC_SPICE_FOLDER_MAX_PATH];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of this file, in bytes.
|
||||||
|
*/
|
||||||
|
uint64_t size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time this file was created, as a UNIX timestamp.
|
||||||
|
*/
|
||||||
|
uint64_t ctime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time this file was last modified, as a UNIX timestamp.
|
||||||
|
*/
|
||||||
|
uint64_t mtime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time this file was last accessed, as a UNIX timestamp.
|
||||||
|
*/
|
||||||
|
uint64_t atime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THe mode field of the file, as retrieved by a call to the stat() family
|
||||||
|
* of functions;
|
||||||
|
*/
|
||||||
|
mode_t stmode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes written to the file.
|
||||||
|
*/
|
||||||
|
uint64_t bytes_written;
|
||||||
|
|
||||||
|
} guac_spice_folder_file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shared folder for the Spice protocol.
|
||||||
|
*/
|
||||||
|
typedef struct guac_spice_folder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guac_client object this folder is associated with.
|
||||||
|
*/
|
||||||
|
guac_client* client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the shared folder.
|
||||||
|
*/
|
||||||
|
char* path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of currently open files in the folder.
|
||||||
|
*/
|
||||||
|
int open_files;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pool of file IDs.
|
||||||
|
*/
|
||||||
|
guac_pool* file_id_pool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All available file structures.
|
||||||
|
*/
|
||||||
|
guac_spice_folder_file files[GUAC_SPICE_FOLDER_MAX_FILES];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether uploads from the client to the shared folder should be disabled.
|
||||||
|
*/
|
||||||
|
int disable_download;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether downloads from the shared folder to the client should be disabled.
|
||||||
|
*/
|
||||||
|
int disable_upload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread which watches the Download folder and triggers the automatic
|
||||||
|
* download of files within this subfolder.
|
||||||
|
*/
|
||||||
|
pthread_t download_thread;
|
||||||
|
|
||||||
|
} guac_spice_folder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a new filesystem given a root path which will be shared with the
|
||||||
|
* user and the remote server via WebDAV.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The guac_client associated with the current RDP session.
|
||||||
|
*
|
||||||
|
* @param folder_path
|
||||||
|
* The local directory to use as the root directory of the shared folder.
|
||||||
|
*
|
||||||
|
* @param create_folder
|
||||||
|
* Non-zero if the folder at the path specified should be automatically
|
||||||
|
* created if it does not yet exist, zero otherwise.
|
||||||
|
*
|
||||||
|
* @param disable_download
|
||||||
|
* Non-zero if downloads from the remote server to the local browser should
|
||||||
|
* be disabled.
|
||||||
|
*
|
||||||
|
* @param disable_upload
|
||||||
|
* Non-zero if uploads from the browser to the remote server should be
|
||||||
|
* disabled.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The newly-allocated filesystem.
|
||||||
|
*/
|
||||||
|
guac_spice_folder* guac_spice_folder_alloc(guac_client* client, const char* folder_path,
|
||||||
|
int create_folder, int disable_download, int disable_upload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the given filesystem.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The folder to free.
|
||||||
|
*/
|
||||||
|
void guac_spice_folder_free(guac_spice_folder* folder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and exposes a new filesystem guac_object to the given user,
|
||||||
|
* providing access to the files within the given Spice shared folder. The
|
||||||
|
* allocated guac_object must eventually be freed via guac_user_free_object().
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The guac_spice_folder object to expose.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The user that the folder should be exposed to.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A new Guacamole filesystem object, configured to use Spice for uploading
|
||||||
|
* and downloading files.
|
||||||
|
*/
|
||||||
|
guac_object* guac_spice_folder_alloc_object(guac_spice_folder* folder, guac_user* user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates the given filename with the given path, separating the two
|
||||||
|
* with a single forward slash. The full result must be no more than
|
||||||
|
* GUAC_SPICE_FOLDER_MAX_PATH bytes long, counting null terminator.
|
||||||
|
*
|
||||||
|
* @param fullpath
|
||||||
|
* The buffer to store the result within. This buffer must be at least
|
||||||
|
* GUAC_SPICE_FOLDER_MAX_PATH bytes long.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* The path to append the filename to.
|
||||||
|
*
|
||||||
|
* @param filename
|
||||||
|
* The filename to append to the path.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Non-zero if the filename is valid and was successfully appended to the
|
||||||
|
* path, zero otherwise.
|
||||||
|
*/
|
||||||
|
int guac_spice_folder_append_filename(char* fullpath, const char* path,
|
||||||
|
const char* filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an arbitrary path, returns a pointer to the first character following
|
||||||
|
* the last path separator in the path (the basename of the path). For example,
|
||||||
|
* given "/foo/bar/baz", this function would return a pointer to "baz".
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* The path to determine the basename of.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A pointer to the first character of the basename within the path.
|
||||||
|
*/
|
||||||
|
const char* guac_spice_folder_basename(const char* path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the given file ID, allowing future open operations to reuse it.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The folder containing the file to close.
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* The ID of the file to close, as returned by guac_spice_folder_open().
|
||||||
|
*/
|
||||||
|
void guac_spice_folder_close(guac_spice_folder* folder, int file_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the file with the given ID.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The folder containing the file to delete.
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* The ID of the file to delete, as returned by guac_spice_folder_open().
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if deletion succeeded, or an error code if an error occurs. All
|
||||||
|
* error codes are negative values and correspond to GUAC_SPICE_FOLDER
|
||||||
|
* constants, such as GUAC_SPICE_FOLDER_ENOENT.
|
||||||
|
*/
|
||||||
|
int guac_spice_folder_delete(guac_spice_folder* folder, int file_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a new filesystem guac_object for the given user, returning the
|
||||||
|
* resulting guac_object. This function is provided for convenience, as it is
|
||||||
|
* can be used as the callback for guac_client_foreach_user() or
|
||||||
|
* guac_client_for_owner(). Note that this guac_object will be tracked
|
||||||
|
* internally by libguac, will be provided to us in the parameters of handlers
|
||||||
|
* related to that guac_object, and will automatically be freed when the
|
||||||
|
* associated guac_user is freed, so the return value of this function can
|
||||||
|
* safely be ignored.
|
||||||
|
*
|
||||||
|
* If either the given user or the given filesystem are NULL, then this
|
||||||
|
* function has no effect.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* The use to expose the filesystem to, or NULL if nothing should be
|
||||||
|
* exposed.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to the guac_spice_folder instance to expose to the given user,
|
||||||
|
* or NULL if nothing should be exposed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The guac_object allocated for the newly-exposed filesystem, or NULL if
|
||||||
|
* no filesystem object could be allocated.
|
||||||
|
*/
|
||||||
|
void* guac_spice_folder_expose(guac_user* user, void* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given errno error code to a GUAC_SPICE_FOLDER error code.
|
||||||
|
*
|
||||||
|
* @param err
|
||||||
|
* The error code, as returned within errno by a system call.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A GUAC_SPICE_FOLDER error code, such as GUAC_SPICE_FOLDER_ENFILE,
|
||||||
|
* GUAC_SPICE_FOLDER_ENOENT, etc.
|
||||||
|
*/
|
||||||
|
int guac_spice_folder_get_errorcode(int err);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file having the given ID, or NULL if no such file exists.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The folder containing the desired file.
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* The ID of the desired, as returned by guac_spice_folder_open().
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The file having the given ID, or NULL is no such file exists.
|
||||||
|
*/
|
||||||
|
guac_spice_folder_file* guac_spice_folder_get_file(guac_spice_folder* folder,
|
||||||
|
int file_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an arbitrary path, which may contain ".." and ".", creates an
|
||||||
|
* absolute path which does NOT contain ".." or ".". The given path MUST
|
||||||
|
* be absolute.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* The path to normalize.
|
||||||
|
*
|
||||||
|
* @param abs_path
|
||||||
|
* The buffer to populate with the normalized path. The normalized path
|
||||||
|
* will not contain relative path components like ".." or ".".
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if normalization succeeded, non-zero otherwise.
|
||||||
|
*/
|
||||||
|
int guac_spice_folder_normalize_path(const char* path, char* abs_path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the given file, returning the a new file ID, or an error code less
|
||||||
|
* than zero if an error occurs. The given path MUST be absolute, and will be
|
||||||
|
* translated to be relative to the drive path of the simulated filesystem.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The shared folder to use when opening the file.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* The absolute path to the file within the simulated filesystem.
|
||||||
|
*
|
||||||
|
* @param flags
|
||||||
|
* A bitwise-OR of various standard POSIX flags to use when opening the
|
||||||
|
* file or directory.
|
||||||
|
*
|
||||||
|
* @param overwrite
|
||||||
|
* True if the file should be overwritten when opening it, otherwise false.
|
||||||
|
*
|
||||||
|
* @param directory
|
||||||
|
* True if the path specified is a directory, otherwise false.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A new file ID, which will always be a positive value, or an error code
|
||||||
|
* if an error occurs. All error codes are negative values and correspond
|
||||||
|
* to GUAC_SPICE_FOLDER constants, such as GUAC_SPICE_FOLDER_ENOENT.
|
||||||
|
*/
|
||||||
|
int guac_spice_folder_open(guac_spice_folder* folder, const char* path,
|
||||||
|
int flags, bool overwrite, bool directory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads up to the given length of bytes from the given offset within the
|
||||||
|
* file having the given ID. Returns the number of bytes read, zero on EOF,
|
||||||
|
* and an error code if an error occurs.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The folder containing the file from which data is to be read.
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* The ID of the file to read data from, as returned by guac_spice_folder_open().
|
||||||
|
*
|
||||||
|
* @param offset
|
||||||
|
* The byte offset within the file to start reading from.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer to fill with data from the file.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The maximum number of bytes to read from the file.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The number of bytes actually read, zero on EOF, or an error code if an
|
||||||
|
* error occurs. All error codes are negative values and correspond to
|
||||||
|
* GUAC_SPICE_FOLDER constants, such as GUAC_SPICE_FOLDER_ENOENT.
|
||||||
|
*/
|
||||||
|
int guac_spice_folder_read(guac_spice_folder* folder, int file_id, uint64_t offset,
|
||||||
|
void* buffer, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next filename within the directory having the given file ID,
|
||||||
|
* or NULL if no more files.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The foleer containing the file to read directory entries from.
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* The ID of the file to read directory entries from, as returned by
|
||||||
|
* guac_spice_folder_open().
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The name of the next filename within the directory, or NULL if the last
|
||||||
|
* file in the directory has already been returned by a previous call.
|
||||||
|
*/
|
||||||
|
const char* guac_spice_folder_read_dir(guac_spice_folder* folder, int file_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes up to the given length of bytes from the given offset within the
|
||||||
|
* file having the given ID. Returns the number of bytes written, and an
|
||||||
|
* error code if an error occurs.
|
||||||
|
*
|
||||||
|
* @param folder
|
||||||
|
* The folder containing the file to which data is to be written.
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* The ID of the file to write data to, as returned by guac_spice_folder_open().
|
||||||
|
*
|
||||||
|
* @param offset
|
||||||
|
* The byte offset within the file to start writinging at.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* The buffer containing the data to write.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The maximum number of bytes to write to the file.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The number of bytes actually written, or an error code if an error
|
||||||
|
* occurs. All error codes are negative values and correspond to
|
||||||
|
* GUAC_SPICE_FOLDER constants, such as GUAC_SPICE_FOLDER_ENOENT.
|
||||||
|
*/
|
||||||
|
int guac_spice_folder_write(guac_spice_folder* folder, int file_id, uint64_t offset,
|
||||||
|
void* buffer, int length);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A handler that is called when the SPICE client receives notification of
|
* A handler that is called when the SPICE client receives notification of
|
||||||
* a new file transfer task.
|
* a new file transfer task.
|
||||||
|
@ -238,12 +238,14 @@ void guac_spice_client_channel_handler(SpiceSession *spice_session,
|
|||||||
|
|
||||||
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||||||
guac_spice_settings* settings = spice_client->settings;
|
guac_spice_settings* settings = spice_client->settings;
|
||||||
int id;
|
int id, type;
|
||||||
|
|
||||||
/* Get the channel ID. */
|
/* Get the channel ID and type. */
|
||||||
g_object_get(channel, SPICE_PROPERTY_CHANNEL_ID, &id, NULL);
|
g_object_get(channel, SPICE_PROPERTY_CHANNEL_ID, &id, NULL);
|
||||||
|
g_object_get(channel, SPICE_PROPERTY_CHANNEL_TYPE, &type, NULL);
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "New channel created: %i", id);
|
guac_client_log(client, GUAC_LOG_DEBUG, "New channel created: %i", id);
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "New channel type: %i", type);
|
||||||
|
|
||||||
/* Check if this is the main channel and register handlers. */
|
/* Check if this is the main channel and register handlers. */
|
||||||
if (SPICE_IS_MAIN_CHANNEL(channel)) {
|
if (SPICE_IS_MAIN_CHANNEL(channel)) {
|
||||||
@ -366,6 +368,11 @@ void guac_spice_client_channel_handler(SpiceSession *spice_session,
|
|||||||
&& strcmp(settings->file_directory, "") != 0) {
|
&& strcmp(settings->file_directory, "") != 0) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "USB redirection is not yet implemented.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Calling spice_channel_connect for channel %d.", id);
|
guac_client_log(client, GUAC_LOG_DEBUG, "Calling spice_channel_connect for channel %d.", id);
|
||||||
if (!spice_channel_connect(channel))
|
if (!spice_channel_connect(channel))
|
||||||
|
@ -58,6 +58,9 @@ const char* GUAC_SPICE_CLIENT_ARGS[] = {
|
|||||||
"file-transfer",
|
"file-transfer",
|
||||||
"file-directory",
|
"file-directory",
|
||||||
"file-transfer-ro",
|
"file-transfer-ro",
|
||||||
|
"file-transfer-create-folder",
|
||||||
|
"disable-download",
|
||||||
|
"disable-upload",
|
||||||
"server-layout",
|
"server-layout",
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
@ -212,6 +215,24 @@ enum SPICE_ARGS_IDX {
|
|||||||
*/
|
*/
|
||||||
IDX_FILE_TRANSFER_RO,
|
IDX_FILE_TRANSFER_RO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not Guacamole should attempt to create the shared folder
|
||||||
|
* if it does not already exist.
|
||||||
|
*/
|
||||||
|
IDX_FILE_TRANSFER_CREATE_FOLDER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "true" if downloads from the remote server to Guacamole client should
|
||||||
|
* be disabled, otherwise false or blank.
|
||||||
|
*/
|
||||||
|
IDX_DISABLE_DOWNLOAD,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "true" if uploads from Guacamole Client to the shared folder should be
|
||||||
|
* disabled, otherwise false or blank.
|
||||||
|
*/
|
||||||
|
IDX_DISABLE_UPLOAD,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the keymap chosen as the layout of the server. Legal names
|
* The name of the keymap chosen as the layout of the server. Legal names
|
||||||
* are defined within the *.keymap files in the "keymaps" directory of the
|
* are defined within the *.keymap files in the "keymaps" directory of the
|
||||||
@ -468,6 +489,21 @@ guac_spice_settings* guac_spice_parse_args(guac_user* user,
|
|||||||
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
||||||
IDX_FILE_TRANSFER_RO, false);
|
IDX_FILE_TRANSFER_RO, false);
|
||||||
|
|
||||||
|
/* Whether or not Guacamole should attempt to create a non-existent folder. */
|
||||||
|
settings->file_transfer_create_folder =
|
||||||
|
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
||||||
|
IDX_FILE_TRANSFER_CREATE_FOLDER, false);
|
||||||
|
|
||||||
|
/* Whether or not downloads (Server -> Client) should be disabled. */
|
||||||
|
settings->disable_download =
|
||||||
|
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
||||||
|
IDX_DISABLE_DOWNLOAD, false);
|
||||||
|
|
||||||
|
/* Whether or not uploads (Client -> Server) should be disabled. */
|
||||||
|
settings->disable_upload =
|
||||||
|
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
|
||||||
|
IDX_DISABLE_UPLOAD, false);
|
||||||
|
|
||||||
/* Pick keymap based on argument */
|
/* Pick keymap based on argument */
|
||||||
settings->server_layout = NULL;
|
settings->server_layout = NULL;
|
||||||
if (argv[IDX_SERVER_LAYOUT][0] != '\0')
|
if (argv[IDX_SERVER_LAYOUT][0] != '\0')
|
||||||
|
@ -128,6 +128,23 @@ typedef struct guac_spice_settings {
|
|||||||
*/
|
*/
|
||||||
bool file_transfer_ro;
|
bool file_transfer_ro;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the folder does not exist and this setting is set to True, guacd
|
||||||
|
* will attempt to create the folder.
|
||||||
|
*/
|
||||||
|
bool file_transfer_create_folder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if downloads (Remote Server -> Guacamole Client) should be
|
||||||
|
* disabled.
|
||||||
|
*/
|
||||||
|
bool disable_download;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if uploads (Guacamole Client -> Remote Server) should be disabled.
|
||||||
|
*/
|
||||||
|
bool disable_upload;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The keymap chosen as the layout of the server.
|
* The keymap chosen as the layout of the server.
|
||||||
*/
|
*/
|
||||||
|
@ -31,6 +31,81 @@
|
|||||||
*/
|
*/
|
||||||
#define GUAC_SPICE_DEFAULT_DISPLAY_ID 0
|
#define GUAC_SPICE_DEFAULT_DISPLAY_ID 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when no more file IDs can be allocated.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_ENFILE -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when no such file exists.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_ENOENT -2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when the operation required a directory
|
||||||
|
* but the file was not a directory.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_ENOTDIR -3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when insufficient space exists to complete
|
||||||
|
* the operation.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_ENOSPC -4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when the operation requires a normal file but
|
||||||
|
* a directory was given.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_EISDIR -5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when permission is denied.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_EACCES -6
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when the operation cannot be completed because the
|
||||||
|
* file already exists.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_EEXIST -7
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when invalid parameters were given.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_EINVAL -8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when the operation is not implemented.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_ENOSYS -9
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code returned when the operation is not supported.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_ENOTSUP -10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of events that can be monitored at a given time for
|
||||||
|
* the Spice shared folder Download folder monitor.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_MAX_EVENTS 256
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of a path in a shared folder.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_MAX_PATH 4096
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of open files in a shared folder.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_MAX_FILES 128
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum level of folder deptch in a shared folder.
|
||||||
|
*/
|
||||||
|
#define GUAC_SPICE_FOLDER_MAX_PATH_DEPTH 64
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The TLS verification value from Guacamole Client that indicates that hostname
|
* The TLS verification value from Guacamole Client that indicates that hostname
|
||||||
* verification should be done.
|
* verification should be done.
|
||||||
@ -79,6 +154,11 @@
|
|||||||
*/
|
*/
|
||||||
#define SPICE_PROPERTY_CHANNEL_ID "channel-id"
|
#define SPICE_PROPERTY_CHANNEL_ID "channel-id"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THe SPICE client channel property that stores the type of the channel.
|
||||||
|
*/
|
||||||
|
#define SPICE_PROPERTY_CHANNEL_TYPE "channel-type"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SPICE library property that determines whether or not the sockets are provided
|
* SPICE library property that determines whether or not the sockets are provided
|
||||||
* by the client.
|
* by the client.
|
||||||
@ -351,6 +431,11 @@
|
|||||||
*/
|
*/
|
||||||
#define SPICE_SIGNAL_RECORD_STOP "record-stop"
|
#define SPICE_SIGNAL_RECORD_STOP "record-stop"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A signal indicating that a share folder is available.
|
||||||
|
*/
|
||||||
|
#define SPICE_SIGNAL_SHARE_FOLDER "notify::share-folder"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The signal indicating that the SPICE server has gone to streaming mode.
|
* The signal indicating that the SPICE server has gone to streaming mode.
|
||||||
*/
|
*/
|
||||||
|
@ -100,6 +100,24 @@ SpiceSession* guac_spice_get_session(guac_client* client) {
|
|||||||
spice_client->keyboard = guac_spice_keyboard_alloc(client,
|
spice_client->keyboard = guac_spice_keyboard_alloc(client,
|
||||||
spice_settings->server_layout);
|
spice_settings->server_layout);
|
||||||
|
|
||||||
|
if (spice_settings->file_transfer) {
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "File transfer enabled, configuring Spice client.");
|
||||||
|
g_object_set(spice_session, SPICE_PROPERTY_SHARED_DIR, spice_settings->file_directory, NULL);
|
||||||
|
g_object_set(spice_session, SPICE_PROPERTY_SHARED_DIR_RO, spice_settings->file_transfer_ro, NULL);
|
||||||
|
spice_client->shared_folder = guac_spice_folder_alloc(client,
|
||||||
|
spice_settings->file_directory,
|
||||||
|
spice_settings->file_transfer_create_folder,
|
||||||
|
spice_settings->disable_download,
|
||||||
|
spice_settings->disable_upload
|
||||||
|
);
|
||||||
|
guac_client_for_owner(client, guac_spice_folder_expose,
|
||||||
|
spice_client->shared_folder);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Disabling file transfer.");
|
||||||
|
g_object_set(spice_session, SPICE_PROPERTY_SHARED_DIR, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Finished setting properties.");
|
guac_client_log(client, GUAC_LOG_DEBUG, "Finished setting properties.");
|
||||||
|
|
||||||
/* Return the configured session. */
|
/* Return the configured session. */
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "channels/file.h"
|
||||||
#include "common/clipboard.h"
|
#include "common/clipboard.h"
|
||||||
#include "common/display.h"
|
#include "common/display.h"
|
||||||
#include "common/iconv.h"
|
#include "common/iconv.h"
|
||||||
@ -114,6 +115,11 @@ typedef struct guac_spice_client {
|
|||||||
*/
|
*/
|
||||||
guac_common_clipboard* clipboard;
|
guac_common_clipboard* clipboard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared folder.
|
||||||
|
*/
|
||||||
|
guac_spice_folder* shared_folder;
|
||||||
|
|
||||||
#ifdef ENABLE_COMMON_SSH
|
#ifdef ENABLE_COMMON_SSH
|
||||||
/**
|
/**
|
||||||
* The user and credentials used to authenticate for SFTP.
|
* The user and credentials used to authenticate for SFTP.
|
||||||
|
Loading…
Reference in New Issue
Block a user