diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index b4dd9202..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 @@ -45,6 +46,8 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path) { 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); @@ -180,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); @@ -692,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 (; idata; + + /* 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