diff --git a/src/protocols/ssh/guac_handlers.c b/src/protocols/ssh/guac_handlers.c index 8293afe2..29fcd404 100644 --- a/src/protocols/ssh/guac_handlers.c +++ b/src/protocols/ssh/guac_handlers.c @@ -24,6 +24,7 @@ #include "client.h" #include "guac_handlers.h" +#include "guac_sftp.h" #include "guac_ssh.h" #include "terminal.h" @@ -101,17 +102,9 @@ int ssh_guac_client_free_handler(guac_client* client) { /* Free channels */ libssh2_channel_free(guac_client_data->term_channel); - /* Shutdown SFTP session, if any */ - if (guac_client_data->sftp_session) - libssh2_sftp_shutdown(guac_client_data->sftp_session); - - /* Disconnect SSH session corresponding to the SFTP session */ - if (guac_client_data->sftp_ssh_session) - guac_common_ssh_destroy_session(guac_client_data->sftp_ssh_session); - /* Clean up the SFTP filesystem object */ if (guac_client_data->sftp_filesystem) - guac_client_free_object(client, guac_client_data->sftp_filesystem); + guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem); /* Free session */ if (guac_client_data->session != NULL) diff --git a/src/protocols/ssh/sftp.c b/src/protocols/ssh/sftp.c index c7fa463b..af6be7bd 100644 --- a/src/protocols/ssh/sftp.c +++ b/src/protocols/ssh/sftp.c @@ -22,517 +22,43 @@ #include "config.h" -#include "guac_json.h" - #include "client.h" +#include "guac_sftp.h" #include "sftp.h" -#include -#include -#include -#include -#include -#include - -#include #include -#include -#include -#include #include -/** - * 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_SFTP_MAX_PATH bytes long, counting null terminator. - * - * @param fullpath - * The buffer to store the result within. This buffer must be at least - * GUAC_SFTP_MAX_PATH bytes long. - * - * @param path - * The path to append the filename to. - * - * @param filename - * The filename to append to the path. - * - * @return - * true if the filename is valid and was successfully appended to the path, - * false otherwise. - */ -static bool guac_ssh_append_filename(char* fullpath, const char* path, - const char* filename) { - - int i; - - /* Disallow "." as a filename */ - if (strcmp(filename, ".") == 0) - return false; - - /* Disallow ".." as a filename */ - if (strcmp(filename, "..") == 0) - return false; - - /* Copy path, append trailing slash */ - for (i=0; i 0 && path[i-1] != '/') - fullpath[i++] = '/'; - break; - } - - /* Copy character if not end of string */ - fullpath[i] = c; - - } - - /* Append filename */ - for (; idata; - char fullpath[GUAC_SFTP_MAX_PATH]; - LIBSSH2_SFTP_HANDLE* file; + guac_object* filesystem = client_data->sftp_filesystem; - /* Concatenate filename with path */ - if (!guac_ssh_append_filename(fullpath, - client_data->sftp_upload_path, filename)) { + /* Handle file upload */ + return guac_common_ssh_sftp_handle_file_stream(filesystem, stream, + mimetype, filename); - guac_client_log(client, GUAC_LOG_DEBUG, - "Filename \"%s\" is invalid or resulting path is too long", - filename); - - /* Abort transfer - invalid filename */ - guac_protocol_send_ack(client->socket, stream, - "SFTP: Illegal filename", - GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); - - guac_socket_flush(client->socket); - return 0; - } - - /* Open file via SFTP */ - file = libssh2_sftp_open(client_data->sftp_session, fullpath, - LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, - S_IRUSR | S_IWUSR); - - /* Inform of status */ - if (file != NULL) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "File \"%s\" opened", - fullpath); - - guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - } - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s", - fullpath, libssh2_sftp_last_error(client_data->sftp_session)); - guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); - guac_socket_flush(client->socket); - } - - /* Set handlers for file stream */ - stream->blob_handler = guac_sftp_blob_handler; - stream->end_handler = guac_sftp_end_handler; - - /* Store file within stream */ - stream->data = file; - return 0; - -} - -int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, - void* data, int length) { - - /* Pull file from stream */ - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; - - /* Attempt write */ - if (libssh2_sftp_write(file, data, length) == length) { - guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes written", length); - guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - } - - /* Inform of any errors */ - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to write to file: %s", - libssh2_sftp_last_error(client_data->sftp_session)); - guac_protocol_send_ack(client->socket, stream, "SFTP: Write failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); - } - - return 0; - -} - -int guac_sftp_end_handler(guac_client* client, guac_stream* stream) { - - /* Pull file from stream */ - LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; - - /* Attempt to close file */ - if (libssh2_sftp_close(file) == 0) { - guac_client_log(client, GUAC_LOG_DEBUG, "File closed"); - guac_protocol_send_ack(client->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - } - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to close file"); - guac_protocol_send_ack(client->socket, stream, "SFTP: Close failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); - } - - return 0; - -} - -int guac_sftp_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; - - /* If successful, read data */ - if (status == GUAC_PROTOCOL_STATUS_SUCCESS) { - - /* Attempt read into buffer */ - char buffer[4096]; - int bytes_read = libssh2_sftp_read(file, buffer, sizeof(buffer)); - - /* If bytes read, send as blob */ - if (bytes_read > 0) { - guac_protocol_send_blob(client->socket, stream, - buffer, bytes_read); - - guac_client_log(client, GUAC_LOG_DEBUG, "%i bytes sent to client", - bytes_read); - - } - - /* If EOF, send end */ - else if (bytes_read == 0) { - guac_client_log(client, GUAC_LOG_DEBUG, "File sent"); - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); - } - - /* Otherwise, fail stream */ - else { - guac_client_log(client, GUAC_LOG_INFO, "Error reading file: %s", - libssh2_sftp_last_error(client_data->sftp_session)); - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); - } - - guac_socket_flush(client->socket); - - } - - /* Otherwise, return stream to client */ - else - guac_client_free_stream(client, stream); - - return 0; } guac_stream* guac_sftp_download_file(guac_client* client, char* filename) { ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_stream* stream; - LIBSSH2_SFTP_HANDLE* file; + guac_object* filesystem = client_data->sftp_filesystem; - /* Attempt to open file for reading */ - file = libssh2_sftp_open(client_data->sftp_session, filename, - LIBSSH2_FXF_READ, 0); - if (file == NULL) { - guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\": %s", - filename, - libssh2_sftp_last_error(client_data->sftp_session)); - return NULL; - } - - /* Allocate stream */ - stream = guac_client_alloc_stream(client); - stream->ack_handler = guac_sftp_ack_handler; - stream->data = file; - - /* Send stream start, strip name */ - filename = basename(filename); - guac_protocol_send_file(client->socket, stream, - "application/octet-stream", filename); - guac_socket_flush(client->socket); - - guac_client_log(client, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename); - return stream; + /* Initiate download of requested file */ + return guac_common_ssh_sftp_download_file(filesystem, filename); } void guac_sftp_set_upload_path(guac_client* client, char* path) { ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - int length = strnlen(path, GUAC_SFTP_MAX_PATH); + guac_object* filesystem = client_data->sftp_filesystem; - /* Ignore requests which exceed maximum-allowed path */ - if (length > GUAC_SFTP_MAX_PATH) { - guac_client_log(client, GUAC_LOG_ERROR, - "Submitted path exceeds limit of %i bytes", - GUAC_SFTP_MAX_PATH); - return; - } - - /* Copy path */ - memcpy(client_data->sftp_upload_path, path, length); - guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path); + /* Set upload path as specified */ + guac_common_ssh_sftp_set_upload_path(filesystem, path); } -guac_object* guac_sftp_expose_filesystem(guac_client* client) { - - /* Init filesystem */ - guac_object* filesystem = guac_client_alloc_object(client); - filesystem->get_handler = guac_sftp_get_handler; - filesystem->put_handler = guac_sftp_put_handler; - - /* Send filesystem to client */ - guac_protocol_send_filesystem(client->socket, filesystem, "/"); - guac_socket_flush(client->socket); - - /* Return allocated filesystem */ - return filesystem; - -} - -int guac_sftp_ls_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status) { - - int bytes_read; - bool blob_written = false; - - char filename[GUAC_SFTP_MAX_PATH]; - LIBSSH2_SFTP_ATTRIBUTES attributes; - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP* sftp = client_data->sftp_session; - guac_sftp_ls_state* list_state = (guac_sftp_ls_state*) stream->data; - - /* If unsuccessful, free stream and abort */ - if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { - libssh2_sftp_closedir(list_state->directory); - guac_client_free_stream(client, stream); - free(list_state); - return 0; - } - - /* While directory entries remain */ - while ((bytes_read = libssh2_sftp_readdir(list_state->directory, - filename, sizeof(filename), &attributes)) > 0 - && !blob_written) { - - char absolute_path[GUAC_SFTP_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_ssh_append_filename(absolute_path, - list_state->directory_name, filename)) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Skipping filename \"%s\" - filename is invalid or " - "resulting path is too long", filename); - - continue; - } - - /* Stat explicitly if symbolic link (might point to directory) */ - if (LIBSSH2_SFTP_S_ISLNK(attributes.permissions)) - libssh2_sftp_stat(sftp, absolute_path, &attributes); - - /* Determine mimetype */ - const char* mimetype; - if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) - mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE; - else - mimetype = "application/octet-stream"; - - /* Write entry */ - blob_written |= guac_common_json_write_property(client, stream, - &list_state->json_state, absolute_path, mimetype); - - } - - /* Complete JSON and cleanup at end of directory */ - if (bytes_read <= 0) { - - /* Complete JSON object */ - guac_common_json_end_object(client, stream, &list_state->json_state); - guac_common_json_flush(client, stream, &list_state->json_state); - - /* Clean up resources */ - libssh2_sftp_closedir(list_state->directory); - free(list_state); - - /* 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_sftp_get_handler(guac_client* client, guac_object* object, - char* name) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP* sftp = client_data->sftp_session; - LIBSSH2_SFTP_ATTRIBUTES attributes; - - /* Attempt to read file information */ - if (libssh2_sftp_stat(sftp, name, &attributes)) { - guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"", - name); - return 0; - } - - /* If directory, send contents of directory */ - if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) { - - /* Open as directory */ - LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name); - if (dir == NULL) { - guac_client_log(client, GUAC_LOG_INFO, - "Unable to read directory \"%s\": %s", - name, libssh2_sftp_last_error(sftp)); - return 0; - } - - /* Init directory listing state */ - guac_sftp_ls_state* list_state = malloc(sizeof(guac_sftp_ls_state)); - - list_state->directory = dir; - strncpy(list_state->directory_name, name, - sizeof(list_state->directory_name)); - - /* Allocate stream for body */ - guac_stream* stream = guac_client_alloc_stream(client); - stream->ack_handler = guac_sftp_ls_ack_handler; - stream->data = list_state; - - /* Init JSON object state */ - guac_common_json_begin_object(client, stream, &list_state->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 { - - /* Open as normal file */ - LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name, - LIBSSH2_FXF_READ, 0); - if (file == NULL) { - guac_client_log(client, GUAC_LOG_INFO, - "Unable to read file \"%s\": %s", - name, libssh2_sftp_last_error(sftp)); - return 0; - } - - /* Allocate stream for body */ - guac_stream* stream = guac_client_alloc_stream(client); - stream->ack_handler = guac_sftp_ack_handler; - stream->data = file; - - /* 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_sftp_put_handler(guac_client* client, guac_object* object, - guac_stream* stream, char* mimetype, char* name) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - LIBSSH2_SFTP* sftp = client_data->sftp_session; - - /* Open file via SFTP */ - LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name, - LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, - S_IRUSR | S_IWUSR); - - /* Acknowledge stream if successful */ - if (file != NULL) { - guac_client_log(client, GUAC_LOG_DEBUG, "File \"%s\" opened", name); - guac_protocol_send_ack(client->socket, stream, "SFTP: File opened", - GUAC_PROTOCOL_STATUS_SUCCESS); - } - - /* Abort on failure */ - else { - guac_client_log(client, GUAC_LOG_INFO, "Unable to open file \"%s\": %s", - name, libssh2_sftp_last_error(sftp)); - guac_protocol_send_ack(client->socket, stream, "SFTP: Open failed", - GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); - } - - /* Set handlers for file stream */ - stream->blob_handler = guac_sftp_blob_handler; - stream->end_handler = guac_sftp_end_handler; - - /* Store file within stream */ - stream->data = file; - - guac_socket_flush(client->socket); - return 0; -} - diff --git a/src/protocols/ssh/sftp.h b/src/protocols/ssh/sftp.h index ecb96714..59cd52e8 100644 --- a/src/protocols/ssh/sftp.h +++ b/src/protocols/ssh/sftp.h @@ -26,161 +26,65 @@ #include "config.h" -#include "guac_json.h" - -#include -#include - #include -#include -#include #include /** - * Maximum number of bytes per path. - */ -#define GUAC_SFTP_MAX_PATH 2048 - -/** - * The current state of a directory listing operation. - */ -typedef struct guac_sftp_ls_state { - - /** - * Reference to the directory currently being listed over SFTP. This - * directory must already be open from a call to libssh2_sftp_opendir(). - */ - LIBSSH2_SFTP_HANDLE* directory; - - /** - * The absolute path of the directory being listed. - */ - char directory_name[GUAC_SFTP_MAX_PATH]; - - /** - * The current state of the JSON directory object being written. - */ - guac_common_json_state json_state; - -} guac_sftp_ls_state; - -/** - * Handler for file messages which begins an SFTP data transfer (upload). + * Handles an incoming stream from a Guacamole "file" instruction, saving the + * contents of that stream to the file having the given name within the + * upload directory set by guac_sftp_set_upload_path(). + * + * @param client + * The client receiving the uploaded file. + * + * @param stream + * The stream through which the uploaded file data will be received. + * + * @param mimetype + * The mimetype of the data being received. + * + * @param filename + * The filename of the file to write to. This filename will always be taken + * relative to the upload path set by + * guac_common_ssh_sftp_set_upload_path(). + * + * @return + * Zero if the incoming stream has been handled successfully, non-zero on + * failure. */ int guac_sftp_file_handler(guac_client* client, guac_stream* stream, char* mimetype, char* filename); /** - * Handler for blob messages which continues an SFTP data transfer (upload). - */ -int guac_sftp_blob_handler(guac_client* client, guac_stream* stream, - void* data, int length); - -/** - * Handler for end messages which ends an SFTP data transfer (upload). - */ -int guac_sftp_end_handler(guac_client* client, guac_stream* stream); - -/** - * Handler for ack messages which continues an SFTP download. - */ -int guac_sftp_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status); - -/** - * Begins (and automatically continues) an SFTP file download to the user. + * Initiates an SFTP file download to the user via the Guacamole "file" + * instruction. The download will be automatically monitored and continued + * after this function terminates in response to "ack" instructions received by + * the client. + * + * @param client + * The client receiving the file. + * + * @param filename + * The filename of the file to download, relative to the given filesystem. + * + * @return + * The file stream created for the file download, already configured to + * properly handle "ack" responses, etc. from the client. */ guac_stream* guac_sftp_download_file(guac_client* client, char* filename); /** - * Set the destination directory for future uploads. + * Sets the destination directory for future uploads submitted via Guacamole + * "file" instruction. This function has no bearing on the destination + * directories of files uploaded with "put" instructions. + * + * @param client + * The client setting the upload path. + * + * @param path + * The path to use for future uploads submitted via "file" instruction. */ void guac_sftp_set_upload_path(guac_client* client, char* path); -/** - * Exposes access to SFTP via a filesystem object, returning that object. The - * object returned must eventually be explicitly freed through a call to - * guac_client_free_object(). - * - * @param client - * The Guacamole client to expose the filesystem to. - * - * @return - * The resulting Guacamole filesystem object, initialized and exposed to - * the client. - */ -guac_object* guac_sftp_expose_filesystem(guac_client* client); - -/** - * Handler for get messages. In context of SFTP 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_sftp_get_handler(guac_client* client, guac_object* object, - char* name); - -/** - * Handler for put messages. In context of SFTP 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_sftp_put_handler(guac_client* client, guac_object* object, - guac_stream* stream, char* mimetype, char* name); - -/** - * Handler for ack messages received due to receipt of a "body" or "blob" - * instruction associated with a SFTP 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_sftp_ls_ack_handler(guac_client* client, guac_stream* stream, - char* message, guac_protocol_status status); - #endif diff --git a/src/protocols/ssh/ssh_client.c b/src/protocols/ssh/ssh_client.c index 1ac672be..93797993 100644 --- a/src/protocols/ssh/ssh_client.c +++ b/src/protocols/ssh/ssh_client.c @@ -23,6 +23,7 @@ #include "config.h" #include "client.h" +#include "guac_sftp.h" #include "guac_ssh.h" #include "sftp.h" #include "terminal.h" @@ -226,31 +227,26 @@ void* ssh_client_thread(void* data) { /* Start SFTP session as well, if enabled */ if (client_data->enable_sftp) { - /* Init handlers for Guacamole-specific console codes */ - client_data->term->upload_path_handler = guac_sftp_set_upload_path; - client_data->term->file_download_handler = guac_sftp_download_file; - /* Create SSH session specific for SFTP */ guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); - client_data->sftp_ssh_session = guac_common_ssh_create_session(client, - client_data->hostname, client_data->port, user); - if (client_data->sftp_ssh_session == NULL) { + guac_common_ssh_session* sftp_ssh_session = + guac_common_ssh_create_session(client, client_data->hostname, + client_data->port, user); + if (sftp_ssh_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } /* Request SFTP */ - client_data->sftp_session = libssh2_sftp_init(client_data->sftp_ssh_session->session); - if (client_data->sftp_session == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to start SFTP session."); - return NULL; - } + client_data->sftp_filesystem = + guac_common_ssh_create_sftp_filesystem(sftp_ssh_session, "/"); - /* Set file handler */ + /* Set generic (non-filesystem) file upload handler */ client->file_handler = guac_sftp_file_handler; - /* Expose filesystem */ - client_data->sftp_filesystem = guac_sftp_expose_filesystem(client); + /* Init handlers for Guacamole-specific console codes */ + client_data->term->upload_path_handler = guac_sftp_set_upload_path; + client_data->term->file_download_handler = guac_sftp_download_file; guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized");