/* * 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-ssh/sftp.h" #include "common-ssh/ssh.h" #include #include #include #include #include #include #include #include #include #include #include int guac_common_ssh_sftp_normalize_path(char* fullpath, const char* path) { int path_depth = 0; const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH]; /* If original path is not absolute, normalization fails */ if (path[0] != '\\' && path[0] != '/') return 0; /* 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_COMMON_SSH_SFTP_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 0; /* 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 == '\\' || 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_COMMON_SSH_SFTP_MAX_DEPTH) return 0; path_components[path_depth++] = current_path_component; } /* Update start of next component */ current_path_component = &(path_scratch[i+1]); } /* end if separator */ } /* end for each character */ /* Add leading slash for resulting absolute path */ fullpath[0] = '/'; /* Append normalized components to path, separated by slashes */ guac_strljoin(fullpath + 1, path_components, path_depth, "/", GUAC_COMMON_SSH_SFTP_MAX_PATH - 1); return 1; } /** * Translates the last error message received by the SFTP layer of an SSH * session into a Guacamole protocol status code. * * @param filesystem * The object (not guac_object) defining the filesystem associated with the * SFTP and SSH sessions. * * @return * The Guacamole protocol status code corresponding to the last reported * error of the SFTP layer, if nay, or GUAC_PROTOCOL_STATUS_SUCCESS if no * error has occurred. */ static guac_protocol_status guac_sftp_get_status( guac_common_ssh_sftp_filesystem* filesystem) { /* Get libssh2 objects */ LIBSSH2_SFTP* sftp = filesystem->sftp_session; LIBSSH2_SESSION* session = filesystem->ssh_session->session; /* Return success code if no error occurred */ if (libssh2_session_last_errno(session) != LIBSSH2_ERROR_SFTP_PROTOCOL) return GUAC_PROTOCOL_STATUS_SUCCESS; /* Translate SFTP error codes defined by * https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 (the most * commonly-implemented standard) */ switch (libssh2_sftp_last_error(sftp)) { /* SSH_FX_OK (not an error) */ case 0: return GUAC_PROTOCOL_STATUS_SUCCESS; /* SSH_FX_EOF (technically not an error) */ case 1: return GUAC_PROTOCOL_STATUS_SUCCESS; /* SSH_FX_NO_SUCH_FILE */ case 2: return GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND; /* SSH_FX_PERMISSION_DENIED */ case 3: return GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN; /* SSH_FX_FAILURE */ case 4: return GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR; /* SSH_FX_BAD_MESSAGE */ case 5: return GUAC_PROTOCOL_STATUS_SERVER_ERROR; /* SSH_FX_NO_CONNECTION / SSH_FX_CONNECTION_LOST */ case 6: case 7: return GUAC_PROTOCOL_STATUS_UPSTREAM_TIMEOUT; /* SSH_FX_OP_UNSUPPORTED */ case 8: return GUAC_PROTOCOL_STATUS_UNSUPPORTED; /* Return generic error if cause unknown */ default: return GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR; } } /** * 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_COMMON_SSH_SFTP_MAX_PATH bytes long, counting null terminator. * * @param fullpath * The buffer to store the result within. This buffer must be at least * GUAC_COMMON_SSH_SFTP_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. */ static int guac_ssh_append_filename(char* fullpath, const char* path, const char* filename) { int length; /* Disallow "." as a filename */ if (strcmp(filename, ".") == 0) return 0; /* Disallow ".." as a filename */ if (strcmp(filename, "..") == 0) return 0; /* Filenames may not contain slashes */ if (strchr(filename, '/') != NULL) return 0; /* Copy base path */ length = guac_strlcpy(fullpath, path, GUAC_COMMON_SSH_SFTP_MAX_PATH); /* * Append trailing slash only if: * 1) Trailing slash is not already present * 2) Path is non-empty */ if (length > 0 && fullpath[length - 1] != '/') length += guac_strlcpy(fullpath + length, "/", GUAC_COMMON_SSH_SFTP_MAX_PATH - length); /* Append filename */ length += guac_strlcpy(fullpath + length, filename, GUAC_COMMON_SSH_SFTP_MAX_PATH - length); /* Verify path length is within maximum */ if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) return 0; /* Append was successful */ return 1; } /** * Concatenates the given paths, separating the two with a single forward * slash. The full result must be no more than GUAC_COMMON_SSH_SFTP_MAX_PATH * bytes long, counting null terminator. * * @param fullpath * The buffer to store the result within. This buffer must be at least * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long. * * @param path_a * The path to place at the beginning of the resulting path. * * @param path_b * The path to append after path_a within the resulting path. * * @return * Non-zero if the paths were successfully concatenated together, zero * otherwise. */ static int guac_ssh_append_path(char* fullpath, const char* path_a, const char* path_b) { int length; /* Copy first half of path */ length = guac_strlcpy(fullpath, path_a, GUAC_COMMON_SSH_SFTP_MAX_PATH); if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) return 0; /* Ensure path ends with trailing slash */ if (length == 0 || fullpath[length - 1] != '/') length += guac_strlcpy(fullpath + length, "/", GUAC_COMMON_SSH_SFTP_MAX_PATH - length); /* Skip past leading slashes in second path */ while (*path_b == '/') path_b++; /* Append final half of path */ length += guac_strlcpy(fullpath + length, path_b, GUAC_COMMON_SSH_SFTP_MAX_PATH - length); /* Verify path length is within maximum */ if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) return 0; /* Append was successful */ return 1; } /** * Handler for blob messages which continue an inbound SFTP data transfer * (upload). The data associated with the given stream is expected to be a * pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data * should be written. * * @param user * The user receiving the blob message. * * @param stream * The Guacamole protocol stream associated with the received blob message. * * @param data * The data received within the blob. * * @param length * The length of the received data, in bytes. * * @return * Zero if the blob is handled successfully, or non-zero on error. */ static int guac_common_ssh_sftp_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { /* Pull file from stream */ LIBSSH2_SFTP_HANDLE* file = (LIBSSH2_SFTP_HANDLE*) stream->data; /* Attempt write */ if (libssh2_sftp_write(file, data, length) == length) { guac_user_log(user, GUAC_LOG_DEBUG, "%i bytes written", length); guac_protocol_send_ack(user->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); } /* Inform of any errors */ else { guac_user_log(user, GUAC_LOG_INFO, "Unable to write to file"); guac_protocol_send_ack(user->socket, stream, "SFTP: Write failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR); guac_socket_flush(user->socket); } return 0; } /** * Handler for end messages which terminate an inbound SFTP data transfer * (upload). The data associated with the given stream is expected to be a * pointer to an open LIBSSH2_SFTP_HANDLE for the file to which the data * has been written and which should now be closed. * * @param user * The user receiving the end message. * * @param stream * The Guacamole protocol stream associated with the received end message. * * @return * Zero if the file is closed successfully, or non-zero on error. */ static int guac_common_ssh_sftp_end_handler(guac_user* user, 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_user_log(user, GUAC_LOG_DEBUG, "File closed"); guac_protocol_send_ack(user->socket, stream, "SFTP: OK", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); } else { guac_user_log(user, GUAC_LOG_INFO, "Unable to close file"); guac_protocol_send_ack(user->socket, stream, "SFTP: Close failed", GUAC_PROTOCOL_STATUS_SERVER_ERROR); guac_socket_flush(user->socket); } return 0; } int guac_common_ssh_sftp_handle_file_stream( guac_common_ssh_sftp_filesystem* filesystem, guac_user* user, guac_stream* stream, char* mimetype, char* filename) { char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; LIBSSH2_SFTP_HANDLE* file; /* Concatenate filename with path */ if (!guac_ssh_append_filename(fullpath, filesystem->upload_path, filename)) { guac_user_log(user, GUAC_LOG_DEBUG, "Filename \"%s\" is invalid or resulting path is too long", filename); /* Abort transfer - invalid filename */ guac_protocol_send_ack(user->socket, stream, "SFTP: Illegal filename", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); guac_socket_flush(user->socket); return 0; } /* Open file via SFTP */ file = libssh2_sftp_open(filesystem->sftp_session, fullpath, LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, S_IRUSR | S_IWUSR); /* Inform of status */ if (file != NULL) { guac_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", fullpath); guac_protocol_send_ack(user->socket, stream, "SFTP: File opened", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); } else { guac_user_log(user, GUAC_LOG_INFO, "Unable to open file \"%s\"", fullpath); guac_protocol_send_ack(user->socket, stream, "SFTP: Open failed", guac_sftp_get_status(filesystem)); guac_socket_flush(user->socket); } /* Set handlers for file stream */ stream->blob_handler = guac_common_ssh_sftp_blob_handler; stream->end_handler = guac_common_ssh_sftp_end_handler; /* Store file within stream */ stream->data = file; return 0; } /** * Handler for ack messages which continue an outbound SFTP data transfer * (download), signalling the current status and requesting additional data. * The data associated with the given stream is expected to be a pointer to an * open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read. * * @param user * The user 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 the ack message. * * @param status * The status code associated with the ack message, which may indicate * success or an error. * * @return * Zero if the file is read from successfully, or non-zero on error. */ static int guac_common_ssh_sftp_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status) { /* Pull file from stream */ 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(user->socket, stream, buffer, bytes_read); guac_user_log(user, GUAC_LOG_DEBUG, "%i bytes sent to user", bytes_read); } /* If bytes could not be read, handle EOF or error condition */ else { /* If EOF, send end */ if (bytes_read == 0) { guac_user_log(user, GUAC_LOG_DEBUG, "File sent"); guac_protocol_send_end(user->socket, stream); guac_user_free_stream(user, stream); } /* Otherwise, fail stream */ else { guac_user_log(user, GUAC_LOG_INFO, "Error reading file"); guac_protocol_send_end(user->socket, stream); guac_user_free_stream(user, stream); } /* Close file */ if (libssh2_sftp_close(file) == 0) guac_user_log(user, GUAC_LOG_DEBUG, "File closed"); else guac_user_log(user, GUAC_LOG_INFO, "Unable to close file"); } guac_socket_flush(user->socket); } /* Otherwise, return stream to user */ else guac_user_free_stream(user, stream); return 0; } guac_stream* guac_common_ssh_sftp_download_file( guac_common_ssh_sftp_filesystem* filesystem, guac_user* user, char* filename) { guac_stream* stream; LIBSSH2_SFTP_HANDLE* file; /* Attempt to open file for reading */ file = libssh2_sftp_open(filesystem->sftp_session, filename, LIBSSH2_FXF_READ, 0); if (file == NULL) { guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"", filename); return NULL; } /* Allocate stream */ stream = guac_user_alloc_stream(user); stream->ack_handler = guac_common_ssh_sftp_ack_handler; stream->data = file; /* Send stream start, strip name */ filename = basename(filename); guac_protocol_send_file(user->socket, stream, "application/octet-stream", filename); guac_socket_flush(user->socket); guac_user_log(user, GUAC_LOG_DEBUG, "Sending file \"%s\"", filename); return stream; } void guac_common_ssh_sftp_set_upload_path( guac_common_ssh_sftp_filesystem* filesystem, const char* path) { guac_client* client = filesystem->ssh_session->client; /* Ignore requests which exceed maximum-allowed path */ int length = strnlen(path, GUAC_COMMON_SSH_SFTP_MAX_PATH)+1; if (length > GUAC_COMMON_SSH_SFTP_MAX_PATH) { guac_client_log(client, GUAC_LOG_ERROR, "Submitted path exceeds limit of %i bytes", GUAC_COMMON_SSH_SFTP_MAX_PATH); return; } /* Copy path */ memcpy(filesystem->upload_path, path, length); guac_client_log(client, GUAC_LOG_DEBUG, "Upload path set to \"%s\"", path); } /** * Handler for ack messages received due to receipt of a "body" or "blob" * instruction associated with a SFTP directory list operation. * * @param user * The user 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. */ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status) { int bytes_read; char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH]; LIBSSH2_SFTP_ATTRIBUTES attributes; guac_common_ssh_sftp_ls_state* list_state = (guac_common_ssh_sftp_ls_state*) stream->data; guac_common_ssh_sftp_filesystem* filesystem = list_state->filesystem; LIBSSH2_SFTP* sftp = filesystem->sftp_session; /* If unsuccessful, free stream and abort */ if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { libssh2_sftp_closedir(list_state->directory); guac_user_free_stream(user, stream); free(list_state); return 0; } /* While directory entries remain */ while ((bytes_read = libssh2_sftp_readdir(list_state->directory, filename, sizeof(filename), &attributes)) > 0) { char absolute_path[GUAC_COMMON_SSH_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_user_log(user, 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_USER_STREAM_INDEX_MIMETYPE; else mimetype = "application/octet-stream"; /* Write entry, waiting for next ack if a blob is written */ if (guac_common_json_write_property(user, stream, &list_state->json_state, absolute_path, mimetype)) break; } /* Complete JSON and cleanup at end of directory */ if (bytes_read <= 0) { /* Complete JSON object */ guac_common_json_end_object(user, stream, &list_state->json_state); guac_common_json_flush(user, stream, &list_state->json_state); /* Clean up resources */ libssh2_sftp_closedir(list_state->directory); free(list_state); /* Signal of stream */ guac_protocol_send_end(user->socket, stream); guac_user_free_stream(user, stream); } guac_socket_flush(user->socket); return 0; } /** * Translates a stream name for the given SFTP filesystem object into the * absolute path corresponding to the actual file it represents. * * @param fullpath * The buffer to populate with the translated path. This buffer MUST be at * least GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size. * * @param object * The Guacamole protocol object associated with the SFTP filesystem. * * @param name * The name of the stream (file) to translate into an absolute path. * * @return * Non-zero if translation succeeded, zero otherwise. */ static int guac_common_ssh_sftp_translate_name(char* fullpath, guac_object* object, char* name) { char normalized_name[GUAC_COMMON_SSH_SFTP_MAX_PATH]; guac_common_ssh_sftp_filesystem* filesystem = (guac_common_ssh_sftp_filesystem*) object->data; /* Normalize stream name into a path, and append to the root path */ return guac_common_ssh_sftp_normalize_path(normalized_name, name) && guac_ssh_append_path(fullpath, filesystem->root_path, normalized_name); } /** * 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 user * The user who sent 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. */ static int guac_common_ssh_sftp_get_handler(guac_user* user, guac_object* object, char* name) { char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; guac_common_ssh_sftp_filesystem* filesystem = (guac_common_ssh_sftp_filesystem*) object->data; LIBSSH2_SFTP* sftp = filesystem->sftp_session; LIBSSH2_SFTP_ATTRIBUTES attributes; /* Translate stream name into filesystem path */ if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) { guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path " "for stream \"%s\"", name); return 0; } /* Attempt to read file information */ if (libssh2_sftp_stat(sftp, fullpath, &attributes)) { guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"", fullpath); 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, fullpath); if (dir == NULL) { guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory \"%s\"", fullpath); return 0; } /* Init directory listing state */ guac_common_ssh_sftp_ls_state* list_state = malloc(sizeof(guac_common_ssh_sftp_ls_state)); list_state->directory = dir; list_state->filesystem = filesystem; int length = guac_strlcpy(list_state->directory_name, name, sizeof(list_state->directory_name)); /* Bail out if directory name is too long to store */ if (length >= sizeof(list_state->directory_name)) { guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory " "\"%s\": Path too long", fullpath); free(list_state); return 0; } /* Allocate stream for body */ guac_stream* stream = guac_user_alloc_stream(user); stream->ack_handler = guac_common_ssh_sftp_ls_ack_handler; stream->data = list_state; /* Init JSON object state */ guac_common_json_begin_object(user, stream, &list_state->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 */ else { /* Open as normal file */ LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath, LIBSSH2_FXF_READ, 0); if (file == NULL) { guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"", fullpath); return 0; } /* Allocate stream for body */ guac_stream* stream = guac_user_alloc_stream(user); stream->ack_handler = guac_common_ssh_sftp_ack_handler; stream->data = file; /* Associate new stream with get request */ guac_protocol_send_body(user->socket, object, stream, "application/octet-stream", name); } guac_socket_flush(user->socket); return 0; } /** * 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 user * The user who sent the put message. * * @param object * The Guacamole protocol object associated with the put request itself. * * @param stream * The Guacamole protocol stream along which the user 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. */ static int guac_common_ssh_sftp_put_handler(guac_user* user, guac_object* object, guac_stream* stream, char* mimetype, char* name) { char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; guac_common_ssh_sftp_filesystem* filesystem = (guac_common_ssh_sftp_filesystem*) object->data; LIBSSH2_SFTP* sftp = filesystem->sftp_session; /* Translate stream name into filesystem path */ if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) { guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path " "for stream \"%s\"", name); return 0; } /* Open file via SFTP */ LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath, LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, S_IRUSR | S_IWUSR); /* Acknowledge stream if successful */ if (file != NULL) { guac_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", fullpath); guac_protocol_send_ack(user->socket, stream, "SFTP: File opened", GUAC_PROTOCOL_STATUS_SUCCESS); } /* Abort on failure */ else { guac_user_log(user, GUAC_LOG_INFO, "Unable to open file \"%s\"", fullpath); guac_protocol_send_ack(user->socket, stream, "SFTP: Open failed", guac_sftp_get_status(filesystem)); } /* Set handlers for file stream */ stream->blob_handler = guac_common_ssh_sftp_blob_handler; stream->end_handler = guac_common_ssh_sftp_end_handler; /* Store file within stream */ stream->data = file; guac_socket_flush(user->socket); return 0; } void* guac_common_ssh_expose_sftp_filesystem(guac_user* user, void* data) { guac_common_ssh_sftp_filesystem* filesystem = (guac_common_ssh_sftp_filesystem*) data; /* No need to expose if there is no filesystem or the user has left */ if (user == NULL || filesystem == NULL) return NULL; /* Allocate and expose filesystem object for user */ return guac_common_ssh_alloc_sftp_filesystem_object(filesystem, user); } guac_object* guac_common_ssh_alloc_sftp_filesystem_object( guac_common_ssh_sftp_filesystem* filesystem, guac_user* user) { /* Init filesystem */ guac_object* fs_object = guac_user_alloc_object(user); fs_object->get_handler = guac_common_ssh_sftp_get_handler; fs_object->put_handler = guac_common_ssh_sftp_put_handler; fs_object->data = filesystem; /* Send filesystem to user */ guac_protocol_send_filesystem(user->socket, fs_object, filesystem->name); guac_socket_flush(user->socket); return fs_object; } guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem( guac_common_ssh_session* session, const char* root_path, const char* name) { /* Request SFTP */ LIBSSH2_SFTP* sftp_session = libssh2_sftp_init(session->session); if (sftp_session == NULL) return NULL; /* Allocate data for SFTP session */ guac_common_ssh_sftp_filesystem* filesystem = malloc(sizeof(guac_common_ssh_sftp_filesystem)); /* Associate SSH session with SFTP data and user */ filesystem->ssh_session = session; filesystem->sftp_session = sftp_session; /* Normalize and store the provided root path */ if (!guac_common_ssh_sftp_normalize_path(filesystem->root_path, root_path)) { guac_client_log(session->client, GUAC_LOG_WARNING, "Cannot create " "SFTP filesystem - \"%s\" is not a valid path.", root_path); free(filesystem); return NULL; } /* Generate filesystem name from root path if no name is provided */ if (name != NULL) filesystem->name = strdup(name); else filesystem->name = strdup(filesystem->root_path); /* Initially upload files to current directory */ strcpy(filesystem->upload_path, "."); /* Return allocated filesystem */ return filesystem; } void guac_common_ssh_destroy_sftp_filesystem( guac_common_ssh_sftp_filesystem* filesystem) { /* Shutdown SFTP session */ libssh2_sftp_shutdown(filesystem->sftp_session); /* Free associated memory */ free(filesystem->name); free(filesystem); }