Merge pull request #49 from glyptodon/rdp-fs

GUAC-1172: Implement filesystem object support for RDP.
This commit is contained in:
James Muehlner 2015-07-06 09:42:38 -07:00
commit 31e90bcdf4
8 changed files with 453 additions and 20 deletions

View File

@ -48,10 +48,10 @@ void guac_common_json_flush(guac_client* client, guac_stream* stream,
} }
bool guac_common_json_write(guac_client* client, guac_stream* stream, int guac_common_json_write(guac_client* client, guac_stream* stream,
guac_common_json_state* json_state, const char* buffer, int length) { guac_common_json_state* json_state, const char* buffer, int length) {
bool blob_written = false; int blob_written = 0;
/* /*
* Append to and flush the JSON buffer as necessary to write the given * Append to and flush the JSON buffer as necessary to write the given
@ -67,7 +67,7 @@ bool guac_common_json_write(guac_client* client, guac_stream* stream,
/* Flush if more room is needed */ /* Flush if more room is needed */
if (json_state->size + blob_length > sizeof(json_state->buffer)) { if (json_state->size + blob_length > sizeof(json_state->buffer)) {
guac_common_json_flush(client, stream, json_state); guac_common_json_flush(client, stream, json_state);
blob_written = true; blob_written = 1;
} }
/* Append data to JSON buffer */ /* Append data to JSON buffer */
@ -86,11 +86,11 @@ bool guac_common_json_write(guac_client* client, guac_stream* stream,
} }
bool guac_common_json_write_string(guac_client* client, int guac_common_json_write_string(guac_client* client,
guac_stream* stream, guac_common_json_state* json_state, guac_stream* stream, guac_common_json_state* json_state,
const char* str) { const char* str) {
bool blob_written = false; int blob_written = 0;
/* Write starting quote */ /* Write starting quote */
blob_written |= guac_common_json_write(client, stream, blob_written |= guac_common_json_write(client, stream,
@ -132,11 +132,11 @@ bool guac_common_json_write_string(guac_client* client,
} }
bool guac_common_json_write_property(guac_client* client, guac_stream* stream, int guac_common_json_write_property(guac_client* client, guac_stream* stream,
guac_common_json_state* json_state, const char* name, guac_common_json_state* json_state, const char* name,
const char* value) { const char* value) {
bool blob_written = false; int blob_written = 0;
/* Write leading comma if not first property */ /* Write leading comma if not first property */
if (json_state->properties_written != 0) if (json_state->properties_written != 0)
@ -173,7 +173,7 @@ void guac_common_json_begin_object(guac_client* client, guac_stream* stream,
} }
bool guac_common_json_end_object(guac_client* client, guac_stream* stream, int guac_common_json_end_object(guac_client* client, guac_stream* stream,
guac_common_json_state* json_state) { guac_common_json_state* json_state) {
/* Write final brace of JSON object */ /* Write final brace of JSON object */

View File

@ -25,8 +25,6 @@
#include "config.h" #include "config.h"
#include <stdbool.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
@ -97,9 +95,9 @@ void guac_common_json_flush(guac_client* client, guac_stream* stream,
* The number of bytes in the buffer. * The number of bytes in the buffer.
* *
* @return * @return
* true if at least one blob was written, false otherwise. * Non-zero if at least one blob was written, zero otherwise.
*/ */
bool guac_common_json_write(guac_client* client, guac_stream* stream, int guac_common_json_write(guac_client* client, guac_stream* stream,
guac_common_json_state* json_state, const char* buffer, int length); guac_common_json_state* json_state, const char* buffer, int length);
/** /**
@ -123,9 +121,9 @@ bool guac_common_json_write(guac_client* client, guac_stream* stream,
* The string to write. * The string to write.
* *
* @return * @return
* true if at least one blob was written, false otherwise. * Non-zero if at least one blob was written, zero otherwise.
*/ */
bool guac_common_json_write_string(guac_client* client, int guac_common_json_write_string(guac_client* client,
guac_stream* stream, guac_common_json_state* json_state, guac_stream* stream, guac_common_json_state* json_state,
const char* str); const char* str);
@ -152,9 +150,9 @@ bool guac_common_json_write_string(guac_client* client,
* The value of the property to write. * The value of the property to write.
* *
* @return * @return
* true if at least one blob was written, false otherwise. * Non-zero if at least one blob was written, zero otherwise.
*/ */
bool guac_common_json_write_property(guac_client* client, guac_stream* stream, int guac_common_json_write_property(guac_client* client, guac_stream* stream,
guac_common_json_state* json_state, const char* name, guac_common_json_state* json_state, const char* name,
const char* value); const char* value);
@ -192,9 +190,9 @@ void guac_common_json_begin_object(guac_client* client, guac_stream* stream,
* The state object whose in-progress JSON object should be terminated. * The state object whose in-progress JSON object should be terminated.
* *
* @return * @return
* true if at least one blob was written, false otherwise. * Non-zero if at least one blob was written, zero otherwise.
*/ */
bool guac_common_json_end_object(guac_client* client, guac_stream* stream, int guac_common_json_end_object(guac_client* client, guac_stream* stream,
guac_common_json_state* json_state); guac_common_json_state* json_state);
#endif #endif

View File

@ -30,6 +30,8 @@
#include <freerdp/utils/svc_plugin.h> #include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#ifdef ENABLE_WINPR #ifdef ENABLE_WINPR
#include <winpr/stream.h> #include <winpr/stream.h>
@ -152,5 +154,10 @@ void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr) {
/* Init data */ /* Init data */
device->data = data->filesystem; device->data = data->filesystem;
/* Announce filesystem to client */
guac_protocol_send_filesystem(rdpdr->client->socket,
data->filesystem->object, "Shared Drive");
guac_socket_flush(rdpdr->client->socket);
} }

View File

@ -24,6 +24,7 @@
#include "rdp_fs.h" #include "rdp_fs.h"
#include "rdp_status.h" #include "rdp_status.h"
#include "rdp_stream.h"
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
@ -36,6 +37,7 @@
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <unistd.h> #include <unistd.h>
#include <guacamole/object.h>
#include <guacamole/pool.h> #include <guacamole/pool.h>
guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path) { guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path) {
@ -43,6 +45,10 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path) {
guac_rdp_fs* fs = malloc(sizeof(guac_rdp_fs)); guac_rdp_fs* fs = malloc(sizeof(guac_rdp_fs));
fs->client = client; fs->client = client;
fs->object = guac_client_alloc_object(client);
fs->object->get_handler = guac_rdp_download_get_handler;
fs->object->put_handler = guac_rdp_upload_put_handler;
fs->drive_path = strdup(drive_path); fs->drive_path = strdup(drive_path);
fs->file_id_pool = guac_pool_alloc(0); fs->file_id_pool = guac_pool_alloc(0);
fs->open_files = 0; fs->open_files = 0;
@ -52,6 +58,7 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path) {
} }
void guac_rdp_fs_free(guac_rdp_fs* fs) { void guac_rdp_fs_free(guac_rdp_fs* fs) {
guac_client_free_object(fs->client, fs->object);
guac_pool_free(fs->file_id_pool); guac_pool_free(fs->file_id_pool);
free(fs->drive_path); free(fs->drive_path);
free(fs); free(fs);
@ -176,7 +183,7 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
path = "\\"; path = "\\";
/* If path is relative, the file does not exist */ /* If path is relative, the file does not exist */
else if (path[0] != '\\') { else if (path[0] != '\\' && path[0] != '/') {
guac_client_log(fs->client, GUAC_LOG_DEBUG, guac_client_log(fs->client, GUAC_LOG_DEBUG,
"%s: Access denied - supplied path \"%s\" is relative.", "%s: Access denied - supplied path \"%s\" is relative.",
__func__, path); __func__, path);
@ -688,3 +695,65 @@ int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info) {
} }
int guac_rdp_fs_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_RDP_FS_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_RDP_FS_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_RDP_FS_MAX_PATH)
return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */
return 1;
}

View File

@ -38,6 +38,7 @@
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/pool.h> #include <guacamole/pool.h>
#include <dirent.h> #include <dirent.h>
@ -272,6 +273,11 @@ typedef struct guac_rdp_fs {
*/ */
guac_client* client; guac_client* client;
/**
* The underlying filesystem object.
*/
guac_object* object;
/** /**
* The root of the filesystem. * The root of the filesystem.
*/ */
@ -422,5 +428,27 @@ int guac_rdp_fs_matches(const char* filename, const char* pattern);
*/ */
int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info); int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info);
/**
* 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_RDP_FS_MAX_PATH bytes long, counting null terminator.
*
* @param fullpath
* The buffer to store the result within. This buffer must be at least
* GUAC_RDP_FS_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_rdp_fs_append_filename(char* fullpath, const char* path,
const char* filename);
#endif #endif

View File

@ -32,6 +32,14 @@
#include "config.h" #include "config.h"
/* Include any constants from winpr/file.h, if available */
#ifdef ENABLE_WINPR
#include <winpr/file.h>
#endif
/* Constants which MAY be defined within FreeRDP */
#ifndef STATUS_SUCCESS #ifndef STATUS_SUCCESS
#define STATUS_SUCCESS 0x00000000 #define STATUS_SUCCESS 0x00000000
#define STATUS_NO_MORE_FILES 0x80000006 #define STATUS_NO_MORE_FILES 0x80000006
@ -53,6 +61,8 @@
#define STATUS_FILE_CLOSED 0xC0000128 #define STATUS_FILE_CLOSED 0xC0000128
#endif #endif
/* Constants which are NEVER defined within FreeRDP */
#define STATUS_FILE_SYSTEM_LIMITATION 0xC0000427 #define STATUS_FILE_SYSTEM_LIMITATION 0xC0000427
#define STATUS_FILE_TOO_LARGE 0xC0000904 #define STATUS_FILE_TOO_LARGE 0xC0000904

View File

@ -356,4 +356,216 @@ int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream,
} }
int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status) {
int blob_written = 0;
const char* filename;
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* If unsuccessful, free stream and abort */
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
guac_rdp_fs_close(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id);
guac_client_free_stream(client, stream);
free(rdp_stream);
return 0;
}
/* While directory entries remain */
while ((filename = guac_rdp_fs_read_dir(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id)) != NULL
&& !blob_written) {
char absolute_path[GUAC_RDP_FS_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_rdp_fs_append_filename(absolute_path,
rdp_stream->ls_status.directory_name, filename)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename);
continue;
}
/* Attempt to open file to determine type */
int file_id = guac_rdp_fs_open(rdp_stream->ls_status.fs, absolute_path,
ACCESS_GENERIC_READ, 0, DISP_FILE_OPEN, 0);
if (file_id < 0)
continue;
/* Get opened file */
guac_rdp_fs_file* file = guac_rdp_fs_get_file(rdp_stream->ls_status.fs,
file_id);
if (file == NULL) {
guac_client_log(rdp_stream->ls_status.fs->client, GUAC_LOG_DEBUG,
"%s: Successful open produced bad file_id: %i",
__func__, file_id);
return 0;
}
/* Determine mimetype */
const char* mimetype;
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY)
mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE;
else
mimetype = "application/octet-stream";
/* Write entry */
blob_written |= guac_common_json_write_property(client, stream,
&rdp_stream->ls_status.json_state, absolute_path, mimetype);
guac_rdp_fs_close(rdp_stream->ls_status.fs, file_id);
}
/* Complete JSON and cleanup at end of directory */
if (filename == NULL) {
/* Complete JSON object */
guac_common_json_end_object(client, stream,
&rdp_stream->ls_status.json_state);
guac_common_json_flush(client, stream,
&rdp_stream->ls_status.json_state);
/* Clean up resources */
guac_rdp_fs_close(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id);
free(rdp_stream);
/* Signal of stream */
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
guac_socket_flush(client->socket);
return 0;
}
int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
char* name) {
/* Get filesystem, ignore request if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
if (fs == NULL)
return 0;
/* Attempt to open file for reading */
int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_READ, 0,
DISP_FILE_OPEN, 0);
if (file_id < 0) {
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"",
name);
return 0;
}
/* Get opened file */
guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
if (file == NULL) {
guac_client_log(fs->client, GUAC_LOG_DEBUG,
"%s: Successful open produced bad file_id: %i",
__func__, file_id);
return 0;
}
/* If directory, send contents of directory */
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) {
/* Create stream data */
guac_rdp_stream* rdp_stream = malloc(sizeof(guac_rdp_stream));
rdp_stream->type = GUAC_RDP_LS_STREAM;
rdp_stream->ls_status.fs = fs;
rdp_stream->ls_status.file_id = file_id;
strncpy(rdp_stream->ls_status.directory_name, name,
sizeof(rdp_stream->ls_status.directory_name));
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_rdp_ls_ack_handler;
stream->data = rdp_stream;
/* Init JSON object state */
guac_common_json_begin_object(client, stream,
&rdp_stream->ls_status.json_state);
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name);
}
/* Otherwise, send file contents */
else {
/* Create stream data */
guac_rdp_stream* rdp_stream = malloc(sizeof(guac_rdp_stream));
rdp_stream->type = GUAC_RDP_DOWNLOAD_STREAM;
rdp_stream->download_status.file_id = file_id;
rdp_stream->download_status.offset = 0;
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->data = rdp_stream;
stream->ack_handler = guac_rdp_download_ack_handler;
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
"application/octet-stream", name);
}
guac_socket_flush(client->socket);
return 0;
}
int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
guac_stream* stream, char* mimetype, char* name) {
/* Get filesystem, return error if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
if (fs == NULL) {
guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(client->socket);
return 0;
}
/* Open file */
int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_WRITE, 0,
DISP_FILE_OVERWRITE_IF, 0);
/* Abort on failure */
if (file_id < 0) {
guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket);
return 0;
}
/* Init upload stream data */
guac_rdp_stream* rdp_stream = malloc(sizeof(guac_rdp_stream));
rdp_stream->type = GUAC_RDP_UPLOAD_STREAM;
rdp_stream->upload_status.offset = 0;
rdp_stream->upload_status.file_id = file_id;
/* Allocate stream, init for file upload */
stream->data = rdp_stream;
stream->blob_handler = guac_rdp_upload_blob_handler;
stream->end_handler = guac_rdp_upload_end_handler;
/* Acknowledge stream creation */
guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
return 0;
}

View File

@ -25,6 +25,7 @@
#define _GUAC_RDP_STREAM_H #define _GUAC_RDP_STREAM_H
#include "config.h" #include "config.h"
#include "guac_json.h"
#include "rdp_svc.h" #include "rdp_svc.h"
#include <guacamole/client.h> #include <guacamole/client.h>
@ -68,6 +69,33 @@ typedef struct guac_rdp_upload_status {
} guac_rdp_upload_status; } guac_rdp_upload_status;
/**
* The current state of a directory listing operation.
*/
typedef struct guac_rdp_ls_status {
/**
* The filesystem associated with the directory being listed.
*/
guac_rdp_fs* fs;
/**
* The file ID of the directory being listed.
*/
int file_id;
/**
* The absolute path of the directory being listed.
*/
char directory_name[GUAC_RDP_FS_MAX_PATH];
/**
* The current state of the JSON directory object being written.
*/
guac_common_json_state json_state;
} guac_rdp_ls_status;
/** /**
* All available stream types. * All available stream types.
*/ */
@ -83,6 +111,11 @@ typedef enum guac_rdp_stream_type {
*/ */
GUAC_RDP_DOWNLOAD_STREAM, GUAC_RDP_DOWNLOAD_STREAM,
/**
* An in-progress stream of a directory listing.
*/
GUAC_RDP_LS_STREAM,
/** /**
* The inbound half of a static virtual channel. * The inbound half of a static virtual channel.
*/ */
@ -115,6 +148,11 @@ typedef struct guac_rdp_stream {
*/ */
guac_rdp_download_status download_status; guac_rdp_download_status download_status;
/**
* The directory list status. Only valid for GUAC_RDP_LS_STREAM.
*/
guac_rdp_ls_status ls_status;
/** /**
* Associated SVC instance. Only valid for GUAC_RDP_INBOUND_SVC_STREAM. * Associated SVC instance. Only valid for GUAC_RDP_INBOUND_SVC_STREAM.
*/ */
@ -174,5 +212,76 @@ int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream);
int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status); char* message, guac_protocol_status status);
/**
* Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a directory list operation.
*
* @param client
* The client receiving the ack message.
*
* @param stream
* The Guacamole protocol stream associated with the received ack message.
*
* @param message
* An arbitrary human-readable message describing the nature of the
* success or failure denoted by this ack message.
*
* @param status
* The status code associated with this ack message, which may indicate
* success or an error.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status);
/**
* 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.
*
* @param client
* The client receiving the get message.
*
* @param object
* The Guacamole protocol object associated with the get request itself.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
char* name);
/**
* 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.
*
* @param client
* The client receiving the put message.
*
* @param object
* The Guacamole protocol object associated with the put request itself.
*
* @param stream
* The Guacamole protocol stream along which the client will be sending
* file data.
*
* @param mimetype
* The mimetype of the data being send along the stream.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
guac_stream* stream, char* mimetype, char* name);
#endif #endif