Merge 0.9.12-incubating changes back to master.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/commit/c4903a8e Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/tree/c4903a8e Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/diff/c4903a8e Branch: refs/heads/master Commit: c4903a8e36eabbaec20ee83a422f599b9db77358 Parents: eb5ef5a 3ff8323 Author: James Muehlner <[email protected]> Authored: Tue Feb 28 10:32:49 2017 -0800 Committer: James Muehlner <[email protected]> Committed: Tue Feb 28 10:32:49 2017 -0800 ---------------------------------------------------------------------- configure.ac | 2 +- doc/Doxyfile | 2 +- src/common-ssh/Makefile.am | 24 +- src/common-ssh/buffer.c | 135 ++++ src/common-ssh/common-ssh/buffer.h | 134 ++++ src/common-ssh/common-ssh/key.h | 170 +++++ src/common-ssh/common-ssh/sftp.h | 242 +++++++ src/common-ssh/common-ssh/ssh.h | 114 ++++ src/common-ssh/common-ssh/user.h | 108 ++++ src/common-ssh/guac_sftp.c | 794 ----------------------- src/common-ssh/guac_sftp.h | 242 ------- src/common-ssh/guac_ssh.c | 544 ---------------- src/common-ssh/guac_ssh.h | 114 ---- src/common-ssh/guac_ssh_buffer.c | 135 ---- src/common-ssh/guac_ssh_buffer.h | 134 ---- src/common-ssh/guac_ssh_key.c | 217 ------- src/common-ssh/guac_ssh_key.h | 170 ----- src/common-ssh/guac_ssh_user.c | 82 --- src/common-ssh/guac_ssh_user.h | 108 ---- src/common-ssh/key.c | 217 +++++++ src/common-ssh/sftp.c | 794 +++++++++++++++++++++++ src/common-ssh/ssh.c | 544 ++++++++++++++++ src/common-ssh/user.c | 82 +++ src/libguac/Makefile.am | 2 +- src/protocols/rdp/client.c | 6 +- src/protocols/rdp/rdp.c | 6 +- src/protocols/rdp/rdp.h | 6 +- src/protocols/rdp/sftp.c | 2 +- src/protocols/ssh/client.c | 4 +- src/protocols/ssh/clipboard.c | 2 +- src/protocols/ssh/input.c | 2 +- src/protocols/ssh/sftp.c | 2 +- src/protocols/ssh/ssh.c | 6 +- src/protocols/ssh/ssh.h | 8 +- src/protocols/telnet/client.c | 2 +- src/protocols/telnet/client.h | 2 +- src/protocols/telnet/clipboard.c | 2 +- src/protocols/telnet/input.c | 2 +- src/protocols/telnet/telnet.c | 2 +- src/protocols/telnet/telnet.h | 2 +- src/protocols/telnet/user.c | 2 +- src/protocols/vnc/client.c | 6 +- src/protocols/vnc/sftp.c | 2 +- src/protocols/vnc/vnc.c | 4 +- src/protocols/vnc/vnc.h | 6 +- src/terminal/Makefile.am | 20 +- src/terminal/buffer.c | 4 +- src/terminal/buffer.h | 129 ---- src/terminal/char_mappings.h | 48 -- src/terminal/common.c | 2 +- src/terminal/common.h | 53 -- src/terminal/display.c | 6 +- src/terminal/display.h | 363 ----------- src/terminal/scrollbar.c | 2 +- src/terminal/scrollbar.h | 357 ----------- src/terminal/terminal.c | 14 +- src/terminal/terminal.h | 839 ------------------------- src/terminal/terminal/buffer.h | 129 ++++ src/terminal/terminal/char_mappings.h | 48 ++ src/terminal/terminal/common.h | 53 ++ src/terminal/terminal/display.h | 363 +++++++++++ src/terminal/terminal/scrollbar.h | 357 +++++++++++ src/terminal/terminal/terminal.h | 839 +++++++++++++++++++++++++ src/terminal/terminal/terminal_handlers.h | 165 +++++ src/terminal/terminal/types.h | 124 ++++ src/terminal/terminal/typescript.h | 184 ++++++ src/terminal/terminal_handlers.c | 8 +- src/terminal/terminal_handlers.h | 165 ----- src/terminal/types.h | 124 ---- src/terminal/typescript.c | 2 +- src/terminal/typescript.h | 184 ------ 71 files changed, 4884 insertions(+), 4884 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/c4903a8e/src/common-ssh/sftp.c ---------------------------------------------------------------------- diff --cc src/common-ssh/sftp.c index 0000000,79db35e..4be1568 mode 000000,100644..100644 --- a/src/common-ssh/sftp.c +++ b/src/common-ssh/sftp.c @@@ -1,0 -1,783 +1,794 @@@ + /* + * 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 <guacamole/client.h> + #include <guacamole/object.h> + #include <guacamole/protocol.h> + #include <guacamole/socket.h> + #include <guacamole/user.h> + #include <libssh2.h> + + #include <fcntl.h> + #include <libgen.h> + #include <stdlib.h> + #include <string.h> + + /** + * 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 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_COMMON_SSH_SFTP_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] != '/') + fullpath[i++] = '/'; + break; + } + + /* Copy character if not end of string */ + fullpath[i] = c; + + } + + /* Append filename */ + for (; i<GUAC_COMMON_SSH_SFTP_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_COMMON_SSH_SFTP_MAX_PATH) + return 0; + + /* Terminate path string */ + fullpath[i] = '\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 EOF, send end */ - else 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 */ ++ /* If bytes could not be read, handle EOF or error condition */ + else { - guac_user_log(user, GUAC_LOG_INFO, "Error reading file"); - guac_protocol_send_end(user->socket, stream); - guac_user_free_stream(user, stream); ++ ++ /* 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; + int blob_written = 0; + + 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 + && !blob_written) { + + 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 */ + blob_written |= guac_common_json_write_property(user, 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(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; + + } + + /** + * 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) { + + guac_common_ssh_sftp_filesystem* filesystem = + (guac_common_ssh_sftp_filesystem*) object->data; + + LIBSSH2_SFTP* sftp = filesystem->sftp_session; + LIBSSH2_SFTP_ATTRIBUTES attributes; + + /* Attempt to read file information */ + if (libssh2_sftp_stat(sftp, name, &attributes)) { + guac_user_log(user, 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_user_log(user, GUAC_LOG_INFO, + "Unable to read directory \"%s\"", name); + 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; + strncpy(list_state->directory_name, name, + sizeof(list_state->directory_name) - 1); + + /* 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, name, + LIBSSH2_FXF_READ, 0); + if (file == NULL) { + guac_user_log(user, GUAC_LOG_INFO, + "Unable to read file \"%s\"", name); + 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) { + + guac_common_ssh_sftp_filesystem* filesystem = + (guac_common_ssh_sftp_filesystem*) object->data; + + LIBSSH2_SFTP* sftp = filesystem->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_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", name); + 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\"", name); + 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* 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->name = strdup(name); + filesystem->ssh_session = session; + filesystem->sftp_session = sftp_session; + + /* 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); + + } + http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/c4903a8e/src/common-ssh/ssh.c ---------------------------------------------------------------------- diff --cc src/common-ssh/ssh.c index 0000000,9d3de19..e4b0d38 mode 000000,100644..100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@@ -1,0 -1,544 +1,544 @@@ + /* + * 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/key.h" + #include "common-ssh/ssh.h" + #include "common-ssh/user.h" + + #include <guacamole/client.h> + #include <libssh2.h> + + #ifdef LIBSSH2_USES_GCRYPT + #include <gcrypt.h> + #endif + + #include <openssl/err.h> + #include <openssl/ssl.h> + + #include <errno.h> + #include <netdb.h> + #include <netinet/in.h> + #include <pthread.h> + #include <stddef.h> + #include <stdlib.h> + #include <string.h> + #include <sys/socket.h> + #include <unistd.h> + + #ifdef LIBSSH2_USES_GCRYPT + GCRY_THREAD_OPTION_PTHREAD_IMPL; + #endif + + /** + * Array of mutexes, used by OpenSSL. + */ + static pthread_mutex_t* guac_common_ssh_openssl_locks = NULL; + + /** + * Called by OpenSSL when locking or unlocking the Nth mutex. + * + * @param mode + * A bitmask denoting the action to be taken on the Nth lock, such as + * CRYPTO_LOCK or CRYPTO_UNLOCK. + * + * @param n + * The index of the lock to lock or unlock. + * + * @param file + * The filename of the function setting the lock, for debugging purposes. + * + * @param line + * The line number of the function setting the lock, for debugging + * purposes. + */ + static void guac_common_ssh_openssl_locking_callback(int mode, int n, + const char* file, int line){ + + /* Lock given mutex upon request */ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&(guac_common_ssh_openssl_locks[n])); + + /* Unlock given mutex upon request */ + else if (mode & CRYPTO_UNLOCK) + pthread_mutex_unlock(&(guac_common_ssh_openssl_locks[n])); + + } + + /** + * Called by OpenSSL when determining the current thread ID. + * + * @return + * An ID which uniquely identifies the current thread. + */ + static unsigned long guac_common_ssh_openssl_id_callback() { + return (unsigned long) pthread_self(); + } + + /** + * Creates the given number of mutexes, such that OpenSSL will have at least + * this number of mutexes at its disposal. + * + * @param count + * The number of mutexes (locks) to create. + */ + static void guac_common_ssh_openssl_init_locks(int count) { + + int i; + + /* Allocate required number of locks */ + guac_common_ssh_openssl_locks = + malloc(sizeof(pthread_mutex_t) * count); + + /* Initialize each lock */ + for (i=0; i < count; i++) + pthread_mutex_init(&(guac_common_ssh_openssl_locks[i]), NULL); + + } + + /** + * Frees the given number of mutexes. + * + * @param count + * The number of mutexes (locks) to free. + */ + static void guac_common_ssh_openssl_free_locks(int count) { + + int i; + + /* SSL lock array was not initialized */ + if (guac_common_ssh_openssl_locks == NULL) + return; + + /* Free all locks */ + for (i=0; i < count; i++) + pthread_mutex_destroy(&(guac_common_ssh_openssl_locks[i])); + + /* Free lock array */ + free(guac_common_ssh_openssl_locks); + + } + + int guac_common_ssh_init(guac_client* client) { + + #ifdef LIBSSH2_USES_GCRYPT + /* Init threadsafety in libgcrypt */ + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + if (!gcry_check_version(GCRYPT_VERSION)) { + guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch."); + return 1; + } + #endif + + /* Init threadsafety in OpenSSL */ + guac_common_ssh_openssl_init_locks(CRYPTO_num_locks()); + CRYPTO_set_id_callback(guac_common_ssh_openssl_id_callback); + CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback); + + /* Init OpenSSL */ + SSL_library_init(); + ERR_load_crypto_strings(); + + /* Init libssh2 */ + libssh2_init(0); + + /* Success */ + return 0; + + } + + void guac_common_ssh_uninit() { + guac_common_ssh_openssl_free_locks(CRYPTO_num_locks()); + } + + /** + * Callback invoked by libssh2 when libssh2_userauth_publickkey() is invoked. + * This callback must sign the given data, returning the signature as newly- + * allocated buffer space. + * + * @param session + * The SSH session for which the signature is being generated. + * + * @param sig + * A pointer to the buffer space containing the signature. This callback + * MUST allocate and assign this space. + * + * @param sig_len + * The length of the signature within the allocated buffer space, in bytes. + * This value must be set to the size of the signature after the signing + * operation completes. + * + * @param data + * The arbitrary data that must be signed. + * + * @param data_len + * The length of the arbitrary data to be signed, in bytes. + * + * @param abstract + * The value of the abstract parameter provided with the corresponding call + * to libssh2_userauth_publickey(). + * + * @return + * Zero on success, non-zero if the signing operation failed. + */ + static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, + unsigned char** sig, size_t* sig_len, + const unsigned char* data, size_t data_len, void **abstract) { + + guac_common_ssh_key* key = (guac_common_ssh_key*) abstract; + int length; + + /* Allocate space for signature */ + *sig = malloc(4096); + + /* Sign with key */ + length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig); + if (length < 0) + return 1; + + *sig_len = length; + return 0; + } + + /** + * Callback for the keyboard-interactive authentication method. Currently + * supports just one prompt for the password. This callback is invoked as + * needed to fullfill a call to libssh2_userauth_keyboard_interactive(). + * + * @param name + * An arbitrary name which should be printed to the terminal for the + * benefit of the user. This is currently ignored. + * + * @param name_len + * The length of the name string, in bytes. + * + * @param instruction + * Arbitrary instructions which should be printed to the terminal for the + * benefit of the user. This is currently ignored. + * + * @param instruction_len + * The length of the instruction string, in bytes. + * + * @param num_prompts + * The number of keyboard-interactive prompts for which responses are + * requested. This callback currently only supports one prompt, and assumes + * that this prompt is requesting the password. + * + * @param prompts + * An array of all keyboard-interactive prompts for which responses are + * requested. + * + * @param responses + * A parallel array into which all prompt responses should be stored. Each + * entry within this array corresponds to the entry in the prompts array + * with the same index. + * + * @param abstract + * The value of the abstract parameter provided when the SSH session was + * created with libssh2_session_init_ex(). + */ + static void guac_common_ssh_kbd_callback(const char *name, int name_len, + const char *instruction, int instruction_len, int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract) { + + guac_common_ssh_session* common_session = + (guac_common_ssh_session*) *abstract; + + guac_client* client = common_session->client; + + /* Send password if only one prompt */ + if (num_prompts == 1) { + char* password = common_session->user->password; + responses[0].text = strdup(password); + responses[0].length = strlen(password); + } + + /* If more than one prompt, a single password is not enough */ + else + guac_client_log(client, GUAC_LOG_WARNING, + "Unsupported number of keyboard-interactive prompts: %i", + num_prompts); + + } + + /** + * Authenticates the user associated with the given session over SSH. All + * required credentials must already be present within the user object + * associated with the given session. + * + * @param session + * The session associated with the user to be authenticated. + * + * @return + * Zero if authentication succeeds, or non-zero if authentication has + * failed. + */ + static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) { + + guac_client* client = common_session->client; + guac_common_ssh_user* user = common_session->user; + LIBSSH2_SESSION* session = common_session->session; + + /* Get user credentials */ + char* username = user->username; + char* password = user->password; + guac_common_ssh_key* key = user->private_key; + + /* Validate username provided */ + if (username == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "SSH authentication requires a username."); + return 1; + } + + /* Get list of supported authentication methods */ + char* user_authlist = libssh2_userauth_list(session, username, + strlen(username)); + guac_client_log(client, GUAC_LOG_DEBUG, + "Supported authentication methods: %s", user_authlist); + + /* Authenticate with private key, if provided */ + if (key != NULL) { + + /* Check if public key auth is supported on the server */ + if (strstr(user_authlist, "publickey") == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication is not supported by " + "the SSH server"); + return 1; + } + + /* Attempt public key auth */ + if (libssh2_userauth_publickey(session, username, + (unsigned char*) key->public_key, key->public_key_length, + guac_common_ssh_sign_callback, (void**) key)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Public key authentication failed: %s", error_message); + + return 1; + + } + + /* Private key authentication succeeded */ + return 0; + + } + + /* Authenticate with password, if provided */ + else if (password != NULL) { + + /* Check if password auth is supported on the server */ + if (strstr(user_authlist, "password") != NULL) { + + /* Attempt password authentication */ + if (libssh2_userauth_password(session, username, password)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Password authentication failed: %s", error_message); + + return 1; + } + + /* Password authentication succeeded */ + return 0; + + } + + /* Check if keyboard-interactive auth is supported on the server */ + if (strstr(user_authlist, "keyboard-interactive") != NULL) { + + /* Attempt keyboard-interactive auth using provided password */ + if (libssh2_userauth_keyboard_interactive(session, username, + &guac_common_ssh_kbd_callback)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Keyboard-interactive authentication failed: %s", + error_message); + + return 1; + } + + /* Keyboard-interactive authentication succeeded */ + return 0; + + } + + /* No known authentication types available */ + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Password and keyboard-interactive authentication are not " + "supported by the SSH server"); + return 1; + + } + + /* No credentials provided */ + guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "SSH authentication requires either a private key or a password."); + return 1; + + } + + guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, + const char* hostname, const char* port, guac_common_ssh_user* user) { + + int retval; + + int fd; + struct addrinfo* addresses; + struct addrinfo* current_address; + + char connected_address[1024]; + char connected_port[64]; + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP + }; + + /* Get socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to create socket: %s", strerror(errno)); + return NULL; + } + + /* Get addresses connection */ + if ((retval = getaddrinfo(hostname, port, &hints, &addresses))) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Error parsing given address or port: %s", + gai_strerror(retval)); + close(fd); + return NULL; + } + + /* Attempt connection to each address until success */ + current_address = addresses; + while (current_address != NULL) { + + /* Resolve hostname */ + if ((retval = getnameinfo(current_address->ai_addr, + current_address->ai_addrlen, + connected_address, sizeof(connected_address), + connected_port, sizeof(connected_port), + NI_NUMERICHOST | NI_NUMERICSERV))) + guac_client_log(client, GUAC_LOG_DEBUG, + "Unable to resolve host: %s", gai_strerror(retval)); + + /* Connect */ + if (connect(fd, current_address->ai_addr, + current_address->ai_addrlen) == 0) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Successfully connected to host %s, port %s", + connected_address, connected_port); + + /* Done if successful connect */ + break; + + } + + /* Otherwise log information regarding bind failure */ + else + guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to " + "host %s, port %s: %s", + connected_address, connected_port, strerror(errno)); + + current_address = current_address->ai_next; + + } + + /* Free addrinfo */ + freeaddrinfo(addresses); + + /* If unable to connect to anything, fail */ + if (current_address == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, ++ guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, + "Unable to connect to any addresses."); + close(fd); + return NULL; + } + + /* Allocate new session */ + guac_common_ssh_session* common_session = + malloc(sizeof(guac_common_ssh_session)); + + /* Open SSH session */ + LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL, + NULL, common_session); + if (session == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Session allocation failed."); + free(common_session); + close(fd); + return NULL; + } + + /* Perform handshake */ + if (libssh2_session_handshake(session, fd)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "SSH handshake failed."); + free(common_session); + close(fd); + return NULL; + } + + /* Store basic session data */ + common_session->client = client; + common_session->user = user; + common_session->session = session; + common_session->fd = fd; + + /* Attempt authentication */ + if (guac_common_ssh_authenticate(common_session)) { + free(common_session); + close(fd); + return NULL; + } + + /* Return created session */ + return common_session; + + } + + void guac_common_ssh_destroy_session(guac_common_ssh_session* session) { + + /* Disconnect and clean up libssh2 */ + libssh2_session_disconnect(session->session, "Bye"); + libssh2_session_free(session->session); + + /* Free all other data */ + free(session); + + } + http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/c4903a8e/src/protocols/rdp/rdp.c ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/c4903a8e/src/protocols/telnet/telnet.c ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/c4903a8e/src/protocols/vnc/vnc.c ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/c4903a8e/src/terminal/terminal.c ----------------------------------------------------------------------
