diff --git a/src/common/guac_json.c b/src/common/guac_json.c index 2125906c..4359d89c 100644 --- a/src/common/guac_json.c +++ b/src/common/guac_json.c @@ -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) { - bool blob_written = false; + int blob_written = 0; /* * 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 */ if (json_state->size + blob_length > sizeof(json_state->buffer)) { guac_common_json_flush(client, stream, json_state); - blob_written = true; + blob_written = 1; } /* 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, const char* str) { - bool blob_written = false; + int blob_written = 0; /* Write starting quote */ 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, const char* value) { - bool blob_written = false; + int blob_written = 0; /* Write leading comma if not first property */ 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) { /* Write final brace of JSON object */ diff --git a/src/common/guac_json.h b/src/common/guac_json.h index 4bab9684..6e0d82d8 100644 --- a/src/common/guac_json.h +++ b/src/common/guac_json.h @@ -25,8 +25,6 @@ #include "config.h" -#include - #include #include @@ -97,9 +95,9 @@ void guac_common_json_flush(guac_client* client, guac_stream* stream, * The number of bytes in the buffer. * * @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); /** @@ -123,9 +121,9 @@ bool guac_common_json_write(guac_client* client, guac_stream* stream, * The string to write. * * @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, const char* str); @@ -152,9 +150,9 @@ bool guac_common_json_write_string(guac_client* client, * The value of the property to write. * * @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, 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. * * @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); #endif diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c b/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c index 027d0744..5ba08c03 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c @@ -30,6 +30,8 @@ #include #include +#include +#include #ifdef ENABLE_WINPR #include @@ -152,5 +154,10 @@ void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr) { /* Init data */ 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); + } diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index 3c2685dc..6a31a95d 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -24,6 +24,7 @@ #include "rdp_fs.h" #include "rdp_status.h" +#include "rdp_stream.h" #include #include @@ -36,6 +37,7 @@ #include #include +#include #include 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)); 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->file_id_pool = guac_pool_alloc(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) { + guac_client_free_object(fs->client, fs->object); guac_pool_free(fs->file_id_pool); free(fs->drive_path); free(fs); @@ -176,7 +183,7 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, path = "\\"; /* 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, "%s: Access denied - supplied path \"%s\" is relative.", __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 0 && path[i-1] != '/' && path[i-1] != '\\') + fullpath[i++] = '/'; + break; + } + + /* Copy character if not end of string */ + fullpath[i] = c; + + } + + /* Append filename */ + for (; i +#include #include #include @@ -272,6 +273,11 @@ typedef struct guac_rdp_fs { */ guac_client* client; + /** + * The underlying filesystem object. + */ + guac_object* object; + /** * 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); +/** + * 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 diff --git a/src/protocols/rdp/rdp_status.h b/src/protocols/rdp/rdp_status.h index 4efe0cca..77be1b0a 100644 --- a/src/protocols/rdp/rdp_status.h +++ b/src/protocols/rdp/rdp_status.h @@ -32,6 +32,14 @@ #include "config.h" +/* Include any constants from winpr/file.h, if available */ + +#ifdef ENABLE_WINPR +#include +#endif + +/* Constants which MAY be defined within FreeRDP */ + #ifndef STATUS_SUCCESS #define STATUS_SUCCESS 0x00000000 #define STATUS_NO_MORE_FILES 0x80000006 @@ -53,7 +61,9 @@ #define STATUS_FILE_CLOSED 0xC0000128 #endif +/* Constants which are NEVER defined within FreeRDP */ + #define STATUS_FILE_SYSTEM_LIMITATION 0xC0000427 #define STATUS_FILE_TOO_LARGE 0xC0000904 -#endif \ No newline at end of file +#endif diff --git a/src/protocols/rdp/rdp_stream.c b/src/protocols/rdp/rdp_stream.c index db90d248..299ba078 100644 --- a/src/protocols/rdp/rdp_stream.c +++ b/src/protocols/rdp/rdp_stream.c @@ -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; +} diff --git a/src/protocols/rdp/rdp_stream.h b/src/protocols/rdp/rdp_stream.h index 3ae0d9bb..fe0b0391 100644 --- a/src/protocols/rdp/rdp_stream.h +++ b/src/protocols/rdp/rdp_stream.h @@ -25,6 +25,7 @@ #define _GUAC_RDP_STREAM_H #include "config.h" +#include "guac_json.h" #include "rdp_svc.h" #include @@ -68,6 +69,33 @@ typedef struct 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. */ @@ -83,6 +111,11 @@ typedef enum guac_rdp_stream_type { */ GUAC_RDP_DOWNLOAD_STREAM, + /** + * An in-progress stream of a directory listing. + */ + GUAC_RDP_LS_STREAM, + /** * The inbound half of a static virtual channel. */ @@ -115,6 +148,11 @@ typedef struct guac_rdp_stream { */ 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. */ @@ -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, 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