diff --git a/Makefile.am b/Makefile.am index c95a0dc0..543cd563 100644 --- a/Makefile.am +++ b/Makefile.am @@ -62,7 +62,7 @@ if ENABLE_TELNET endif if ENABLE_VNC -#SUBDIRS += src/protocols/vnc +SUBDIRS += src/protocols/vnc endif EXTRA_DIST = \ diff --git a/src/protocols/vnc/Makefile.am b/src/protocols/vnc/Makefile.am index 86db0a75..f909ff2d 100644 --- a/src/protocols/vnc/Makefile.am +++ b/src/protocols/vnc/Makefile.am @@ -26,16 +26,28 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-vnc.la libguac_client_vnc_la_SOURCES = \ + auth.c \ client.c \ clipboard.c \ - guac_handlers.c \ - vnc_handlers.c + cursor.c \ + display.c \ + input.c \ + log.c \ + settings.c \ + user.c \ + vnc.c noinst_HEADERS = \ + auth.h \ client.h \ clipboard.h \ - guac_handlers.h \ - vnc_handlers.h + cursor.h \ + display.h \ + input.h \ + log.h \ + settings.h \ + user.h \ + vnc.h # Optional PulseAudio support if ENABLE_PULSE diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c new file mode 100644 index 00000000..84c0af5c --- /dev/null +++ b/src/protocols/vnc/auth.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "auth.h" +#include "vnc.h" + +#include +#include +#include + +char* guac_vnc_get_password(rfbClient* client) { + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + return ((guac_vnc_client*) gc->data)->settings->password; +} + diff --git a/src/protocols/vnc/auth.h b/src/protocols/vnc/auth.h new file mode 100644 index 00000000..bb716c9e --- /dev/null +++ b/src/protocols/vnc/auth.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_VNC_AUTH_H +#define GUAC_VNC_AUTH_H + +#include "config.h" + +#include +#include + +/** + * Handler which returns the configured password. + */ +char* guac_vnc_get_password(rfbClient* client); + +#endif + diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 7d999d4a..a0a3f631 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -23,16 +23,8 @@ #include "config.h" #include "client.h" -#include "clipboard.h" -#include "guac_clipboard.h" -#include "guac_dot_cursor.h" -#include "guac_handlers.h" -#include "guac_pointer_cursor.h" -#include "vnc_handlers.h" - -#ifdef ENABLE_PULSE -#include "pulse.h" -#endif +#include "user.h" +#include "vnc.h" #ifdef ENABLE_COMMON_SSH #include "guac_sftp.h" @@ -40,522 +32,103 @@ #include "sftp.h" #endif -#include -#include +#ifdef ENABLE_PULSE +#include "pulse.h" +#endif + #include -#include -#include #include #include -#include -/* Client plugin arguments */ -const char* GUAC_CLIENT_ARGS[] = { - "hostname", - "port", - "read-only", - "encodings", - "password", - "swap-red-blue", - "color-depth", - "cursor", - "autoretry", - "clipboard-encoding", +int guac_client_init(guac_client* client) { -#ifdef ENABLE_VNC_REPEATER - "dest-host", - "dest-port", -#endif - -#ifdef ENABLE_PULSE - "enable-audio", - "audio-servername", -#endif - -#ifdef ENABLE_VNC_LISTEN - "reverse-connect", - "listen-timeout", -#endif - -#ifdef ENABLE_COMMON_SSH - "enable-sftp", - "sftp-hostname", - "sftp-port", - "sftp-username", - "sftp-password", - "sftp-private-key", - "sftp-passphrase", - "sftp-directory", -#endif - - NULL -}; - -enum VNC_ARGS_IDX { - - IDX_HOSTNAME, - IDX_PORT, - IDX_READ_ONLY, - IDX_ENCODINGS, - IDX_PASSWORD, - IDX_SWAP_RED_BLUE, - IDX_COLOR_DEPTH, - IDX_CURSOR, - IDX_AUTORETRY, - IDX_CLIPBOARD_ENCODING, - -#ifdef ENABLE_VNC_REPEATER - IDX_DEST_HOST, - IDX_DEST_PORT, -#endif - -#ifdef ENABLE_PULSE - IDX_ENABLE_AUDIO, - IDX_AUDIO_SERVERNAME, -#endif - -#ifdef ENABLE_VNC_LISTEN - IDX_REVERSE_CONNECT, - IDX_LISTEN_TIMEOUT, -#endif - -#ifdef ENABLE_COMMON_SSH - IDX_ENABLE_SFTP, - IDX_SFTP_HOSTNAME, - IDX_SFTP_PORT, - IDX_SFTP_USERNAME, - IDX_SFTP_PASSWORD, - IDX_SFTP_PRIVATE_KEY, - IDX_SFTP_PASSPHRASE, - IDX_SFTP_DIRECTORY, -#endif - - VNC_ARGS_COUNT -}; - -char* __GUAC_CLIENT = "GUAC_CLIENT"; - -/** - * Allocates a new rfbClient instance given the parameters stored within the - * client, returning NULL on failure. - */ -static rfbClient* __guac_vnc_get_client(guac_client* client) { - - rfbClient* rfb_client = rfbGetClient(8, 3, 4); /* 32-bpp client */ - vnc_guac_client_data* guac_client_data = - (vnc_guac_client_data*) client->data; - - /* Store Guac client in rfb client */ - rfbClientSetClientData(rfb_client, __GUAC_CLIENT, client); - - /* Framebuffer update handler */ - rfb_client->GotFrameBufferUpdate = guac_vnc_update; - rfb_client->GotCopyRect = guac_vnc_copyrect; - - /* Do not handle clipboard and local cursor if read-only */ - if (guac_client_data->read_only == 0) { - - /* Clipboard */ - rfb_client->GotXCutText = guac_vnc_cut_text; - - /* Set remote cursor */ - if (guac_client_data->remote_cursor) - rfb_client->appData.useRemoteCursor = FALSE; - - else { - /* Enable client-side cursor */ - rfb_client->appData.useRemoteCursor = TRUE; - rfb_client->GotCursorShape = guac_vnc_cursor; - } - } - - /* Password */ - rfb_client->GetPassword = guac_vnc_get_password; - - /* Depth */ - guac_vnc_set_pixel_format(rfb_client, guac_client_data->color_depth); - - /* Hook into allocation so we can handle resize. */ - guac_client_data->rfb_MallocFrameBuffer = rfb_client->MallocFrameBuffer; - rfb_client->MallocFrameBuffer = guac_vnc_malloc_framebuffer; - rfb_client->canHandleNewFBSize = 1; - - /* Set hostname and port */ - rfb_client->serverHost = strdup(guac_client_data->hostname); - rfb_client->serverPort = guac_client_data->port; - -#ifdef ENABLE_VNC_REPEATER - /* Set repeater parameters if specified */ - if (guac_client_data->dest_host) { - rfb_client->destHost = strdup(guac_client_data->dest_host); - rfb_client->destPort = guac_client_data->dest_port; - } -#endif - -#ifdef ENABLE_VNC_LISTEN - /* If reverse connection enabled, start listening */ - if (guac_client_data->reverse_connect) { - - guac_client_log(client, GUAC_LOG_INFO, "Listening for connections on port %i", - guac_client_data->port); - - /* Listen for connection from server */ - rfb_client->listenPort = guac_client_data->port; - if (listenForIncomingConnectionsNoFork(rfb_client, - guac_client_data->listen_timeout*1000) <= 0) - return NULL; - - } -#endif - - /* Set encodings if provided */ - if (guac_client_data->encodings) - rfb_client->appData.encodingsString = - strdup(guac_client_data->encodings); - - /* Connect */ - if (rfbInitClient(rfb_client, NULL, NULL)) - return rfb_client; - - /* If connection fails, return NULL */ - return NULL; - -} - -/** - * Sets the encoding of clipboard data exchanged with the VNC server to the - * encoding having the given name. If the name is left blank, or is invalid, - * the standard ISO8859-1 encoding will be used. - * - * @param client - * The client to set the clipboard encoding of. - * - * @param name - * The name of the encoding to use for all clipboard data. Valid values - * are: "ISO8859-1", "UTF-8", "UTF-16", "CP1252", or "". - * - * @return - * Zero if the chosen encoding is standard for VNC, or non-zero if the VNC - * standard is being violated. - */ -static int guac_vnc_set_clipboard_encoding(guac_client* client, - const char* name) { - - vnc_guac_client_data* guac_client_data = - (vnc_guac_client_data*) client->data; - - /* Use ISO8859-1 if explicitly selected or blank */ - if (name[0] == '\0' || strcmp(name, "ISO8859-1") == 0) { - guac_client_data->clipboard_reader = GUAC_READ_ISO8859_1; - guac_client_data->clipboard_writer = GUAC_WRITE_ISO8859_1; - return 0; - } - - /* UTF-8 */ - if (strcmp(name, "UTF-8") == 0) { - guac_client_data->clipboard_reader = GUAC_READ_UTF8; - guac_client_data->clipboard_writer = GUAC_WRITE_UTF8; - return 1; - } - - /* UTF-16 */ - if (strcmp(name, "UTF-16") == 0) { - guac_client_data->clipboard_reader = GUAC_READ_UTF16; - guac_client_data->clipboard_writer = GUAC_WRITE_UTF16; - return 1; - } - - /* CP1252 */ - if (strcmp(name, "CP1252") == 0) { - guac_client_data->clipboard_reader = GUAC_READ_CP1252; - guac_client_data->clipboard_writer = GUAC_WRITE_CP1252; - return 1; - } - - /* If encoding unrecognized, warn and default to ISO8859-1 */ - guac_client_log(client, GUAC_LOG_WARNING, - "Encoding '%s' is invalid. Defaulting to ISO8859-1.", name); - - guac_client_data->clipboard_reader = GUAC_READ_ISO8859_1; - guac_client_data->clipboard_writer = GUAC_WRITE_ISO8859_1; - return 0; - -} - -int guac_client_init(guac_client* client, int argc, char** argv) { - - rfbClient* rfb_client; - - vnc_guac_client_data* guac_client_data; - - int retries_remaining; - - /* Set up libvncclient logging */ - rfbClientLog = guac_vnc_client_log_info; - rfbClientErr = guac_vnc_client_log_error; - - /*** PARSE ARGUMENTS ***/ - - if (argc != VNC_ARGS_COUNT) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Wrong argument count received."); - return 1; - } + /* Set client args */ + client->args = GUAC_VNC_CLIENT_ARGS; /* Alloc client data */ - guac_client_data = malloc(sizeof(vnc_guac_client_data)); - client->data = guac_client_data; - - guac_client_data->hostname = strdup(argv[IDX_HOSTNAME]); - guac_client_data->port = atoi(argv[IDX_PORT]); - guac_client_data->password = strdup(argv[IDX_PASSWORD]); /* NOTE: freed by libvncclient */ - guac_client_data->default_surface = NULL; - - /* Set flags */ - guac_client_data->remote_cursor = (strcmp(argv[IDX_CURSOR], "remote") == 0); - guac_client_data->swap_red_blue = (strcmp(argv[IDX_SWAP_RED_BLUE], "true") == 0); - guac_client_data->read_only = (strcmp(argv[IDX_READ_ONLY], "true") == 0); - - /* Parse color depth */ - guac_client_data->color_depth = atoi(argv[IDX_COLOR_DEPTH]); - -#ifdef ENABLE_VNC_REPEATER - /* Set repeater parameters if specified */ - if (argv[IDX_DEST_HOST][0] != '\0') - guac_client_data->dest_host = strdup(argv[IDX_DEST_HOST]); - else - guac_client_data->dest_host = NULL; - - if (argv[IDX_DEST_PORT][0] != '\0') - guac_client_data->dest_port = atoi(argv[IDX_DEST_PORT]); -#endif - - /* Set encodings if specified */ - if (argv[IDX_ENCODINGS][0] != '\0') - guac_client_data->encodings = strdup(argv[IDX_ENCODINGS]); - else - guac_client_data->encodings = NULL; - - /* Parse autoretry */ - if (argv[IDX_AUTORETRY][0] != '\0') - retries_remaining = atoi(argv[IDX_AUTORETRY]); - else - retries_remaining = 0; - -#ifdef ENABLE_VNC_LISTEN - /* Set reverse-connection flag */ - guac_client_data->reverse_connect = - (strcmp(argv[IDX_REVERSE_CONNECT], "true") == 0); - - /* Parse listen timeout */ - if (argv[IDX_LISTEN_TIMEOUT][0] != '\0') - guac_client_data->listen_timeout = atoi(argv[IDX_LISTEN_TIMEOUT]); - else - guac_client_data->listen_timeout = 5000; -#endif + guac_vnc_client* vnc_client = calloc(1, sizeof(guac_vnc_client)); + client->data = vnc_client; /* Init clipboard */ - guac_client_data->clipboard = guac_common_clipboard_alloc(GUAC_VNC_CLIPBOARD_MAX_LENGTH); - - /* Configure clipboard encoding */ - if (guac_vnc_set_clipboard_encoding(client, argv[IDX_CLIPBOARD_ENCODING])) - guac_client_log(client, GUAC_LOG_INFO, - "Using non-standard VNC clipboard encoding: '%s'.", - argv[IDX_CLIPBOARD_ENCODING]); - - /* Ensure connection is kept alive during lengthy connects */ - guac_socket_require_keep_alive(client->socket); - - /* Attempt connection */ - rfb_client = __guac_vnc_get_client(client); - - /* If unsuccessful, retry as many times as specified */ - while (!rfb_client && retries_remaining > 0) { - - struct timespec guac_vnc_connect_interval = { - .tv_sec = GUAC_VNC_CONNECT_INTERVAL/1000, - .tv_nsec = (GUAC_VNC_CONNECT_INTERVAL%1000)*1000000 - }; - - guac_client_log(client, GUAC_LOG_INFO, - "Connect failed. Waiting %ims before retrying...", - GUAC_VNC_CONNECT_INTERVAL); - - /* Wait for given interval then retry */ - nanosleep(&guac_vnc_connect_interval, NULL); - rfb_client = __guac_vnc_get_client(client); - retries_remaining--; - - } - - /* If the final connect attempt fails, return error */ - if (!rfb_client) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to connect to VNC server."); - return 1; - } - -#ifdef ENABLE_PULSE - guac_client_data->audio_enabled = - (strcmp(argv[IDX_ENABLE_AUDIO], "true") == 0); - - /* If an encoding is available, load an audio stream */ - if (guac_client_data->audio_enabled) { - - guac_client_data->audio = guac_audio_stream_alloc(client, NULL, - GUAC_VNC_AUDIO_RATE, - GUAC_VNC_AUDIO_CHANNELS, - GUAC_VNC_AUDIO_BPS); - - /* Load servername if specified */ - if (argv[IDX_AUDIO_SERVERNAME][0] != '\0') - guac_client_data->pa_servername = - strdup(argv[IDX_AUDIO_SERVERNAME]); - else - guac_client_data->pa_servername = NULL; - - /* If successful, init audio system */ - if (guac_client_data->audio != NULL) { - - guac_client_log(client, GUAC_LOG_INFO, - "Audio will be encoded as %s", - guac_client_data->audio->encoder->mimetype); - - /* Require threadsafe sockets if audio enabled */ - guac_socket_require_threadsafe(client->socket); - - /* Start audio stream */ - guac_pa_start_stream(client); - - } - - /* Otherwise, audio loading failed */ - else - guac_client_log(client, GUAC_LOG_INFO, - "No available audio encoding. Sound disabled."); - - } /* end if audio enabled */ -#endif - -#ifdef ENABLE_COMMON_SSH - guac_common_ssh_init(client); - - /* Connect via SSH if SFTP is enabled */ - if (strcmp(argv[IDX_ENABLE_SFTP], "true") == 0) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Connecting via SSH for SFTP filesystem access."); - - guac_client_data->sftp_user = - guac_common_ssh_create_user(argv[IDX_SFTP_USERNAME]); - - /* Import private key, if given */ - if (argv[IDX_SFTP_PRIVATE_KEY][0] != '\0') { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Authenticating with private key."); - - /* Abort if private key cannot be read */ - if (guac_common_ssh_user_import_key(guac_client_data->sftp_user, - argv[IDX_SFTP_PRIVATE_KEY], - argv[IDX_SFTP_PASSPHRASE])) { - guac_common_ssh_destroy_user(guac_client_data->sftp_user); - return 1; - } - - } - - /* Otherwise, use specified password */ - else { - guac_client_log(client, GUAC_LOG_DEBUG, - "Authenticating with password."); - guac_common_ssh_user_set_password(guac_client_data->sftp_user, - argv[IDX_SFTP_PASSWORD]); - } - - /* Parse hostname - use VNC hostname by default */ - const char* sftp_hostname = argv[IDX_SFTP_HOSTNAME]; - if (sftp_hostname[0] == '\0') - sftp_hostname = guac_client_data->hostname; - - /* Parse port, defaulting to standard SSH port */ - const char* sftp_port = argv[IDX_SFTP_PORT]; - if (sftp_port[0] == '\0') - sftp_port = "22"; - - /* Attempt SSH connection */ - guac_client_data->sftp_session = - guac_common_ssh_create_session(client, sftp_hostname, sftp_port, - guac_client_data->sftp_user); - - /* Fail if SSH connection does not succeed */ - if (guac_client_data->sftp_session == NULL) { - /* Already aborted within guac_common_ssh_create_session() */ - guac_common_ssh_destroy_user(guac_client_data->sftp_user); - return 1; - } - - /* Load and expose filesystem */ - guac_client_data->sftp_filesystem = - guac_common_ssh_create_sftp_filesystem( - guac_client_data->sftp_session, "/"); - - /* Abort if SFTP connection fails */ - if (guac_client_data->sftp_filesystem == NULL) { - guac_common_ssh_destroy_session(guac_client_data->sftp_session); - guac_common_ssh_destroy_user(guac_client_data->sftp_user); - return 1; - } - - /* Configure destination for basic uploads, if specified */ - if (argv[IDX_SFTP_DIRECTORY][0] != '\0') - guac_common_ssh_sftp_set_upload_path( - guac_client_data->sftp_filesystem, - argv[IDX_SFTP_DIRECTORY]); - - /* Set file handler for basic uploads */ - client->file_handler = guac_vnc_sftp_file_handler; - - guac_client_log(client, GUAC_LOG_DEBUG, - "SFTP connection succeeded."); - - } -#endif - - /* Set remaining client data */ - guac_client_data->rfb_client = rfb_client; - guac_client_data->copy_rect_used = 0; - guac_client_data->cursor = guac_client_alloc_buffer(client); + vnc_client->clipboard = guac_common_clipboard_alloc(GUAC_VNC_CLIPBOARD_MAX_LENGTH); /* Set handlers */ - client->handle_messages = vnc_guac_client_handle_messages; - client->free_handler = vnc_guac_client_free_handler; + client->join_handler = guac_vnc_user_join_handler; + client->free_handler = guac_vnc_client_free_handler; - /* If not read-only, set input handlers and pointer */ - if (guac_client_data->read_only == 0) { + return 0; +} - /* Only handle mouse/keyboard/clipboard if not read-only */ - client->mouse_handler = vnc_guac_client_mouse_handler; - client->key_handler = vnc_guac_client_key_handler; - client->clipboard_handler = guac_vnc_clipboard_handler; +int guac_vnc_client_free_handler(guac_client* client) { - /* If not read-only but cursor is remote, set a dot cursor */ - if (guac_client_data->remote_cursor) - guac_common_set_dot_cursor(client); + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_vnc_settings* settings = vnc_client->settings; - /* Otherwise, set pointer until explicitly requested otherwise */ - else - guac_common_set_pointer_cursor(client); + /* Clean up VNC client*/ + rfbClient* rfb_client = vnc_client->rfb_client; + if (rfb_client != NULL) { + + /* Wait for client thread to finish */ + pthread_join(vnc_client->client_thread, NULL); + + /* Free memory not free'd by libvncclient's rfbClientCleanup() */ + if (rfb_client->frameBuffer != NULL) free(rfb_client->frameBuffer); + if (rfb_client->raw_buffer != NULL) free(rfb_client->raw_buffer); + if (rfb_client->rcSource != NULL) free(rfb_client->rcSource); + + /* Free VNC rfbClientData linked list (not free'd by rfbClientCleanup()) */ + while (rfb_client->clientData != NULL) { + rfbClientData* next = rfb_client->clientData->next; + free(rfb_client->clientData); + rfb_client->clientData = next; + } + + rfbClientCleanup(rfb_client); } - /* Send name */ - guac_protocol_send_name(client->socket, rfb_client->desktopName); +#ifdef ENABLE_COMMON_SSH + /* Free SFTP filesystem, if loaded */ + if (vnc_client->sftp_filesystem) + guac_common_ssh_destroy_sftp_filesystem(vnc_client->sftp_filesystem); + + /* Free SFTP session */ + if (vnc_client->sftp_session) + guac_common_ssh_destroy_session(vnc_client->sftp_session); + + /* Free SFTP user */ + if (vnc_client->sftp_user) + guac_common_ssh_destroy_user(vnc_client->sftp_user); + + guac_common_ssh_uninit(); +#endif + + /* Free clipboard */ + if (vnc_client->clipboard != NULL) + guac_common_clipboard_free(vnc_client->clipboard); + + /* Free display */ + if (vnc_client->display != NULL) + guac_common_display_free(vnc_client->display); + + /* Free settings-dependend data */ + if (settings != NULL) { + +#ifdef ENABLE_PULSE + /* If audio enabled, stop streaming */ + if (settings->audio_enabled) + guac_pa_stop_stream(client); +#endif + + /* Free parsed settings */ + guac_vnc_settings_free(settings); + + } + + /* Free generic data struct */ + free(client->data); - /* Create default surface */ - guac_client_data->default_surface = guac_common_surface_alloc(client, - client->socket, GUAC_DEFAULT_LAYER, - rfb_client->width, rfb_client->height); return 0; - } diff --git a/src/protocols/vnc/client.h b/src/protocols/vnc/client.h index 428ffff9..d7ac99c7 100644 --- a/src/protocols/vnc/client.h +++ b/src/protocols/vnc/client.h @@ -24,24 +24,7 @@ #ifndef __GUAC_VNC_CLIENT_H #define __GUAC_VNC_CLIENT_H -#include "config.h" -#include "guac_clipboard.h" -#include "guac_surface.h" -#include "guac_iconv.h" - -#include -#include -#include - -#ifdef ENABLE_PULSE -#include -#endif - -#ifdef ENABLE_COMMON_SSH -#include "guac_sftp.h" -#include "guac_ssh.h" -#include "guac_ssh_user.h" -#endif +#include /** * The maximum duration of a frame in milliseconds. @@ -65,162 +48,10 @@ */ #define GUAC_VNC_CLIPBOARD_MAX_LENGTH 262144 -extern char* __GUAC_CLIENT; - /** - * VNC-specific client data. + * Handler which frees all data associated with the guac_client. */ -typedef struct vnc_guac_client_data { - - /** - * The underlying VNC client. - */ - rfbClient* rfb_client; - - /** - * The original framebuffer malloc procedure provided by the initialized - * rfbClient. - */ - MallocFrameBufferProc rfb_MallocFrameBuffer; - - /** - * Whether copyrect was used to produce the latest update received - * by the VNC server. - */ - int copy_rect_used; - - /** - * The hostname of the VNC server (or repeater) to connect to. - */ - char* hostname; - - /** - * The port of the VNC server (or repeater) to connect to. - */ - int port; - - /** - * The password given in the arguments. - */ - char* password; - - /** - * Space-separated list of encodings to use within the VNC session. - */ - char* encodings; - - /** - * Whether the red and blue components of each color should be swapped. - * This is mainly used for VNC servers that do not properly handle - * colors. - */ - int swap_red_blue; - - /** - * The color depth to request, in bits. - */ - int color_depth; - - /** - * Whether this connection is read-only, and user input should be dropped. - */ - int read_only; - - /** - * The VNC host to connect to, if using a repeater. - */ - char* dest_host; - - /** - * The VNC port to connect to, if using a repeater. - */ - int dest_port; - -#ifdef ENABLE_VNC_LISTEN - /** - * Whether not actually connecting to a VNC server, but rather listening - * for a connection from the VNC server (reverse connection). - */ - int reverse_connect; - - /** - * The maximum amount of time to wait when listening for connections, in - * milliseconds. - */ - int listen_timeout; -#endif - - /** - * Whether the cursor should be rendered on the server (remote) or on the - * client (local). - */ - int remote_cursor; - - /** - * The layer holding the cursor image. - */ - guac_layer* cursor; - - /** - * Whether audio is enabled. - */ - int audio_enabled; - - /** - * Audio output, if any. - */ - guac_audio_stream* audio; - -#ifdef ENABLE_PULSE - /** - * The name of the PulseAudio server to connect to. - */ - char* pa_servername; - - /** - * PulseAudio event loop. - */ - pa_threaded_mainloop* pa_mainloop; -#endif - - /** - * Internal clipboard. - */ - guac_common_clipboard* clipboard; - - /** - * Default surface. - */ - guac_common_surface* default_surface; - -#ifdef ENABLE_COMMON_SSH - /** - * The user and credentials used to authenticate for SFTP. - */ - guac_common_ssh_user* sftp_user; - - /** - * The SSH session used for SFTP. - */ - guac_common_ssh_session* sftp_session; - - /** - * The exposed filesystem object, implemented with SFTP. - */ - guac_object* sftp_filesystem; -#endif - - /** - * Clipboard encoding-specific reader. - */ - guac_iconv_read* clipboard_reader; - - /** - * Clipboard encoding-specific writer. - */ - guac_iconv_write* clipboard_writer; - -} vnc_guac_client_data; +int guac_vnc_client_free_handler(guac_client* client); #endif diff --git a/src/protocols/vnc/clipboard.c b/src/protocols/vnc/clipboard.c index 6f3a391b..f6d69f13 100644 --- a/src/protocols/vnc/clipboard.c +++ b/src/protocols/vnc/clipboard.c @@ -25,17 +25,64 @@ #include "clipboard.h" #include "guac_clipboard.h" #include "guac_iconv.h" +#include "user.h" +#include "vnc.h" #include #include +#include #include +#include -int guac_vnc_clipboard_handler(guac_client* client, guac_stream* stream, +int guac_vnc_set_clipboard_encoding(guac_client* client, + const char* name) { + + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + + /* Use ISO8859-1 if explicitly selected or NULL */ + if (name == NULL || strcmp(name, "ISO8859-1") == 0) { + vnc_client->clipboard_reader = GUAC_READ_ISO8859_1; + vnc_client->clipboard_writer = GUAC_WRITE_ISO8859_1; + return 0; + } + + /* UTF-8 */ + if (strcmp(name, "UTF-8") == 0) { + vnc_client->clipboard_reader = GUAC_READ_UTF8; + vnc_client->clipboard_writer = GUAC_WRITE_UTF8; + return 1; + } + + /* UTF-16 */ + if (strcmp(name, "UTF-16") == 0) { + vnc_client->clipboard_reader = GUAC_READ_UTF16; + vnc_client->clipboard_writer = GUAC_WRITE_UTF16; + return 1; + } + + /* CP1252 */ + if (strcmp(name, "CP1252") == 0) { + vnc_client->clipboard_reader = GUAC_READ_CP1252; + vnc_client->clipboard_writer = GUAC_WRITE_CP1252; + return 1; + } + + /* If encoding unrecognized, warn and default to ISO8859-1 */ + guac_client_log(client, GUAC_LOG_WARNING, + "Encoding '%s' is invalid. Defaulting to ISO8859-1.", name); + + vnc_client->clipboard_reader = GUAC_READ_ISO8859_1; + vnc_client->clipboard_writer = GUAC_WRITE_ISO8859_1; + return 0; + +} + +int guac_vnc_clipboard_handler(guac_user* user, guac_stream* stream, char* mimetype) { /* Clear clipboard and prepare for new data */ - vnc_guac_client_data* client_data = (vnc_guac_client_data*) client->data; - guac_common_clipboard_reset(client_data->clipboard, mimetype); + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + guac_common_clipboard_reset(vnc_client->clipboard, mimetype); /* Set handlers for clipboard stream */ stream->blob_handler = guac_vnc_clipboard_blob_handler; @@ -44,29 +91,29 @@ int guac_vnc_clipboard_handler(guac_client* client, guac_stream* stream, return 0; } -int guac_vnc_clipboard_blob_handler(guac_client* client, guac_stream* stream, +int guac_vnc_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { /* Append new data */ - vnc_guac_client_data* client_data = (vnc_guac_client_data*) client->data; - guac_common_clipboard_append(client_data->clipboard, (char*) data, length); + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + guac_common_clipboard_append(vnc_client->clipboard, (char*) data, length); return 0; } -int guac_vnc_clipboard_end_handler(guac_client* client, guac_stream* stream) { +int guac_vnc_clipboard_end_handler(guac_user* user, guac_stream* stream) { - vnc_guac_client_data* client_data = (vnc_guac_client_data*) client->data; - rfbClient* rfb_client = client_data->rfb_client; + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + rfbClient* rfb_client = vnc_client->rfb_client; char output_data[GUAC_VNC_CLIPBOARD_MAX_LENGTH]; - const char* input = client_data->clipboard->buffer; + const char* input = vnc_client->clipboard->buffer; char* output = output_data; - guac_iconv_write* writer = client_data->clipboard_writer; + guac_iconv_write* writer = vnc_client->clipboard_writer; /* Convert clipboard contents */ - guac_iconv(GUAC_READ_UTF8, &input, client_data->clipboard->length, + guac_iconv(GUAC_READ_UTF8, &input, vnc_client->clipboard->length, writer, &output, sizeof(output_data)); /* Send via VNC */ @@ -75,3 +122,25 @@ int guac_vnc_clipboard_end_handler(guac_client* client, guac_stream* stream) { return 0; } +void guac_vnc_cut_text(rfbClient* client, const char* text, int textlen) { + + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + char received_data[GUAC_VNC_CLIPBOARD_MAX_LENGTH]; + + const char* input = text; + char* output = received_data; + guac_iconv_read* reader = vnc_client->clipboard_reader; + + /* Convert clipboard contents */ + guac_iconv(reader, &input, textlen, + GUAC_WRITE_UTF8, &output, sizeof(received_data)); + + /* Send converted data */ + guac_common_clipboard_reset(vnc_client->clipboard, "text/plain"); + guac_common_clipboard_append(vnc_client->clipboard, received_data, output - received_data); + guac_common_clipboard_send(vnc_client->clipboard, gc); + +} + diff --git a/src/protocols/vnc/clipboard.h b/src/protocols/vnc/clipboard.h index 5ff2ef79..66a2372b 100644 --- a/src/protocols/vnc/clipboard.h +++ b/src/protocols/vnc/clipboard.h @@ -25,25 +25,52 @@ #include "config.h" +#include "guac_clipboard.h" + #include #include +#include +#include +#include /** - * Handler for inbound clipboard data. + * Sets the encoding of clipboard data exchanged with the VNC server to the + * encoding having the given name. If the name is NULL, or is invalid, the + * standard ISO8859-1 encoding will be used. + * + * @param client + * The client to set the clipboard encoding of. + * + * @param name + * The name of the encoding to use for all clipboard data. Valid values + * are: "ISO8859-1", "UTF-8", "UTF-16", "CP1252", or NULL. + * + * @return + * Zero if the chosen encoding is standard for VNC, or non-zero if the VNC + * standard is being violated. */ -int guac_vnc_clipboard_handler(guac_client* client, guac_stream* stream, - char* mimetype); +int guac_vnc_set_clipboard_encoding(guac_client* client, + const char* name); + +/** + * Handler for inbound clipboard data from Guacamole users. + */ +int guac_vnc_clipboard_handler(guac_user* user, guac_stream* stream, char* mimetype); /** * Handler for stream data related to clipboard. */ -int guac_vnc_clipboard_blob_handler(guac_client* client, guac_stream* stream, - void* data, int length); +int guac_vnc_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length); /** * Handler for end-of-stream related to clipboard. */ -int guac_vnc_clipboard_end_handler(guac_client* client, guac_stream* stream); +int guac_vnc_clipboard_end_handler(guac_user* user, guac_stream* stream); + +/** + * Handler for clipboard data received via VNC. + */ +void guac_vnc_cut_text(rfbClient* client, const char* text, int textlen); #endif diff --git a/src/protocols/vnc/cursor.c b/src/protocols/vnc/cursor.c new file mode 100644 index 00000000..b7a92082 --- /dev/null +++ b/src/protocols/vnc/cursor.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "client.h" +#include "guac_cursor.h" +#include "guac_display.h" +#include "guac_surface.h" +#include "vnc.h" + +#include +#include +#include +#include +#include +#include +#include + +/* Define cairo_format_stride_for_width() if missing */ +#ifndef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH +#define cairo_format_stride_for_width(format, width) (width*4) +#endif + +#include +#include +#include +#include + +void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp) { + + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + /* Cairo image buffer */ + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); + unsigned char* buffer = malloc(h*stride); + unsigned char* buffer_row_current = buffer; + + /* VNC image buffer */ + unsigned int fb_stride = bpp * w; + unsigned char* fb_row_current = client->rcSource; + unsigned char* fb_mask = client->rcMask; + + int dx, dy; + + /* Copy image data from VNC client to RGBA buffer */ + for (dy = 0; dy> client->format.redShift) * 0x100 / (client->format.redMax + 1); + green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax+ 1); + blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); + + /* Output ARGB */ + if (vnc_client->settings->swap_red_blue) + *(buffer_current++) = (alpha << 24) | (blue << 16) | (green << 8) | red; + else + *(buffer_current++) = (alpha << 24) | (red << 16) | (green << 8) | blue; + + /* Next VNC pixel */ + fb_current += bpp; + + } + } + + /* Update stored cursor information */ + guac_common_cursor_set_argb(vnc_client->display->cursor, x, y, + buffer, w, h, stride); + + /* Free surface */ + free(buffer); + + /* libvncclient does not free rcMask as it does rcSource */ + free(client->rcMask); +} + diff --git a/src/protocols/vnc/cursor.h b/src/protocols/vnc/cursor.h new file mode 100644 index 00000000..7eb8f5e1 --- /dev/null +++ b/src/protocols/vnc/cursor.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_DISPLAY_CURSOR_H +#define GUAC_DISPLAY_CURSOR_H + +#include "config.h" + +#include +#include + +void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp); + +#endif + diff --git a/src/protocols/vnc/display.c b/src/protocols/vnc/display.c new file mode 100644 index 00000000..fbc740a4 --- /dev/null +++ b/src/protocols/vnc/display.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "client.h" +#include "guac_iconv.h" +#include "guac_surface.h" +#include "vnc.h" + +#include +#include +#include +#include +#include +#include +#include + +/* Define cairo_format_stride_for_width() if missing */ +#ifndef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH +#define cairo_format_stride_for_width(format, width) (width*4) +#endif + +#include +#include +#include +#include + +void guac_vnc_update(rfbClient* client, int x, int y, int w, int h) { + + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + int dx, dy; + + /* Cairo image buffer */ + int stride; + unsigned char* buffer; + unsigned char* buffer_row_current; + cairo_surface_t* surface; + + /* VNC framebuffer */ + unsigned int bpp; + unsigned int fb_stride; + unsigned char* fb_row_current; + + /* Ignore extra update if already handled by copyrect */ + if (vnc_client->copy_rect_used) { + vnc_client->copy_rect_used = 0; + return; + } + + /* Init Cairo buffer */ + stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); + buffer = malloc(h*stride); + buffer_row_current = buffer; + + bpp = client->format.bitsPerPixel/8; + fb_stride = bpp * client->width; + fb_row_current = client->frameBuffer + (y * fb_stride) + (x * bpp); + + /* Copy image data from VNC client to PNG */ + for (dy = y; dy> client->format.redShift) * 0x100 / (client->format.redMax + 1); + green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax+ 1); + blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); + + /* Output RGB */ + if (vnc_client->settings->swap_red_blue) + *(buffer_current++) = (blue << 16) | (green << 8) | red; + else + *(buffer_current++) = (red << 16) | (green << 8) | blue; + + fb_current += bpp; + + } + } + + /* For now, only use default layer */ + surface = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_RGB24, w, h, stride); + + guac_common_surface_draw(vnc_client->display->default_surface, + x, y, surface); + + /* Free surface */ + cairo_surface_destroy(surface); + free(buffer); + +} + +void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y) { + + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + /* For now, only use default layer */ + guac_common_surface_copy(vnc_client->display->default_surface, + src_x, src_y, w, h, + vnc_client->display->default_surface, dest_x, dest_y); + + vnc_client->copy_rect_used = 1; + +} + +void guac_vnc_set_pixel_format(rfbClient* client, int color_depth) { + switch(color_depth) { + case 8: + client->format.depth = 8; + client->format.bitsPerPixel = 8; + client->format.blueShift = 6; + client->format.redShift = 0; + client->format.greenShift = 3; + client->format.blueMax = 3; + client->format.redMax = 7; + client->format.greenMax = 7; + break; + + case 16: + client->format.depth = 16; + client->format.bitsPerPixel = 16; + client->format.blueShift = 0; + client->format.redShift = 11; + client->format.greenShift = 5; + client->format.blueMax = 0x1f; + client->format.redMax = 0x1f; + client->format.greenMax = 0x3f; + break; + + case 24: + case 32: + default: + client->format.depth = 24; + client->format.bitsPerPixel = 32; + client->format.blueShift = 0; + client->format.redShift = 16; + client->format.greenShift = 8; + client->format.blueMax = 0xff; + client->format.redMax = 0xff; + client->format.greenMax = 0xff; + } +} + +rfbBool guac_vnc_malloc_framebuffer(rfbClient* rfb_client) { + + guac_client* gc = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + /* Resize surface */ + if (vnc_client->display != NULL) + guac_common_surface_resize(vnc_client->display->default_surface, + rfb_client->width, rfb_client->height); + + /* Use original, wrapped proc */ + return vnc_client->rfb_MallocFrameBuffer(rfb_client); +} + diff --git a/src/protocols/vnc/vnc_handlers.h b/src/protocols/vnc/display.h similarity index 79% rename from src/protocols/vnc/vnc_handlers.h rename to src/protocols/vnc/display.h index 7e6870d1..e9eaf77b 100644 --- a/src/protocols/vnc/vnc_handlers.h +++ b/src/protocols/vnc/display.h @@ -20,23 +20,34 @@ * THE SOFTWARE. */ - -#ifndef __GUAC_VNC_VNC_HANDLERS_H -#define __GUAC_VNC_VNC_HANDLERS_H +#ifndef GUAC_VNC_DISPLAY_H +#define GUAC_VNC_DISPLAY_H #include "config.h" #include +#include -void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp); +/** + * Called for normal binary VNC image data. + */ void guac_vnc_update(rfbClient* client, int x, int y, int w, int h); + +/** + * Called for updates which contain data from existing rectangles of the + * display. + */ void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y); -char* guac_vnc_get_password(rfbClient* client); -rfbBool guac_vnc_malloc_framebuffer(rfbClient* rfb_client); -void guac_vnc_cut_text(rfbClient* client, const char* text, int textlen); -void guac_vnc_client_log_info(const char* format, ...); -void guac_vnc_client_log_error(const char* format, ...); + +/** + * Called when the pixel format of future updates is changing. + */ void guac_vnc_set_pixel_format(rfbClient* client, int color_depth); +/** + * Called when the display is being resized (or initially allocated). + */ +rfbBool guac_vnc_malloc_framebuffer(rfbClient* rfb_client); + #endif diff --git a/src/protocols/vnc/guac_handlers.c b/src/protocols/vnc/guac_handlers.c deleted file mode 100644 index be380df0..00000000 --- a/src/protocols/vnc/guac_handlers.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "config.h" - -#include "client.h" -#include "guac_clipboard.h" -#include "guac_surface.h" - -#include -#include -#include -#include - -#ifdef ENABLE_COMMON_SSH -#include -#include -#include -#endif - -#ifdef ENABLE_PULSE -#include "pulse.h" -#endif - -#include - -/** - * Waits until data is available to be read from the given rfbClient, and thus - * a call to HandleRFBServerMessages() should not block. If the timeout elapses - * before data is available, zero is returned. - * - * @param rfb_client - * The rfbClient to wait for. - * - * @param timeout - * The maximum amount of time to wait, in microseconds. - * - * @returns - * A positive value if data is available, zero if the timeout elapses - * before data becomes available, or a negative value on error. - */ -static int guac_vnc_wait_for_messages(rfbClient* rfb_client, int timeout) { - - /* Do not explicitly wait while data is on the buffer */ - if (rfb_client->buffered) - return 1; - - /* If no data on buffer, wait for data on socket */ - return WaitForMessage(rfb_client, timeout); - -} - -int vnc_guac_client_handle_messages(guac_client* client) { - - vnc_guac_client_data* guac_client_data = (vnc_guac_client_data*) client->data; - rfbClient* rfb_client = guac_client_data->rfb_client; - - /* Initially wait for messages */ - int wait_result = guac_vnc_wait_for_messages(rfb_client, 1000000); - guac_timestamp frame_start = guac_timestamp_current(); - while (wait_result > 0) { - - guac_timestamp frame_end; - int frame_remaining; - - /* Handle any message received */ - if (!HandleRFBServerMessage(rfb_client)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Error handling message from VNC server."); - return 1; - } - - /* Calculate time remaining in frame */ - frame_end = guac_timestamp_current(); - frame_remaining = frame_start + GUAC_VNC_FRAME_DURATION - frame_end; - - /* Wait again if frame remaining */ - if (frame_remaining > 0) - wait_result = guac_vnc_wait_for_messages(rfb_client, - GUAC_VNC_FRAME_TIMEOUT*1000); - else - break; - - } - - /* If an error occurs, log it and fail */ - if (wait_result < 0) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Connection closed."); - return 1; - } - - guac_common_surface_flush(guac_client_data->default_surface); - return 0; - -} - -int vnc_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) { - - rfbClient* rfb_client = ((vnc_guac_client_data*) client->data)->rfb_client; - - SendPointerEvent(rfb_client, x, y, mask); - - return 0; -} - -int vnc_guac_client_key_handler(guac_client* client, int keysym, int pressed) { - - rfbClient* rfb_client = ((vnc_guac_client_data*) client->data)->rfb_client; - - SendKeyEvent(rfb_client, keysym, pressed); - - return 0; -} - -int vnc_guac_client_free_handler(guac_client* client) { - - vnc_guac_client_data* guac_client_data = (vnc_guac_client_data*) client->data; - rfbClient* rfb_client = guac_client_data->rfb_client; - -#ifdef ENABLE_PULSE - /* If audio enabled, stop streaming */ - if (guac_client_data->audio_enabled) - guac_pa_stop_stream(client); -#endif - -#ifdef ENABLE_COMMON_SSH - /* Free SFTP filesystem, if loaded */ - if (guac_client_data->sftp_filesystem) - guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem); - - /* Free SFTP session */ - if (guac_client_data->sftp_session) - guac_common_ssh_destroy_session(guac_client_data->sftp_session); - - /* Free SFTP user */ - if (guac_client_data->sftp_user) - guac_common_ssh_destroy_user(guac_client_data->sftp_user); - - guac_common_ssh_uninit(); -#endif - - /* Free encodings string, if used */ - if (guac_client_data->encodings != NULL) - free(guac_client_data->encodings); - - /* Free clipboard */ - guac_common_clipboard_free(guac_client_data->clipboard); - - /* Free surface */ - guac_common_surface_free(guac_client_data->default_surface); - - /* Free generic data struct */ - free(client->data); - - /* Free memory not free'd by libvncclient's rfbClientCleanup() */ - if (rfb_client->frameBuffer != NULL) free(rfb_client->frameBuffer); - if (rfb_client->raw_buffer != NULL) free(rfb_client->raw_buffer); - if (rfb_client->rcSource != NULL) free(rfb_client->rcSource); - - /* Free VNC rfbClientData linked list (not free'd by rfbClientCleanup()) */ - while (rfb_client->clientData != NULL) { - rfbClientData* next = rfb_client->clientData->next; - free(rfb_client->clientData); - rfb_client->clientData = next; - } - - /* Clean up VNC client*/ - rfbClientCleanup(rfb_client); - - return 0; -} - diff --git a/src/protocols/vnc/input.c b/src/protocols/vnc/input.c new file mode 100644 index 00000000..fbe8119d --- /dev/null +++ b/src/protocols/vnc/input.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "guac_cursor.h" +#include "guac_display.h" +#include "vnc.h" + +#include +#include + +int guac_vnc_user_mouse_handler(guac_user* user, int x, int y, int mask) { + + guac_client* client = user->client; + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + + /* Store current mouse location */ + guac_common_cursor_move(vnc_client->display->cursor, user, x, y); + + SendPointerEvent(vnc_client->rfb_client, x, y, mask); + + return 0; +} + +int guac_vnc_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + + SendKeyEvent(vnc_client->rfb_client, keysym, pressed); + + return 0; +} + diff --git a/src/protocols/vnc/guac_handlers.h b/src/protocols/vnc/input.h similarity index 75% rename from src/protocols/vnc/guac_handlers.h rename to src/protocols/vnc/input.h index 64879410..d1923351 100644 --- a/src/protocols/vnc/guac_handlers.h +++ b/src/protocols/vnc/input.h @@ -21,17 +21,22 @@ */ -#ifndef __GUAC_VNC_GUAC_HANDLERS_H -#define __GUAC_VNC_GUAC_HANDLERS_H +#ifndef GUAC_VNC_INPUT_H +#define GUAC_VNC_INPUT_H #include "config.h" -#include +#include -int vnc_guac_client_handle_messages(guac_client* client); -int vnc_guac_client_mouse_handler(guac_client* client, int x, int y, int mask); -int vnc_guac_client_key_handler(guac_client* client, int keysym, int pressed); -int vnc_guac_client_free_handler(guac_client* client); +/** + * Handler for Guacamole user mouse events. + */ +int guac_vnc_user_mouse_handler(guac_user* user, int x, int y, int mask); + +/** + * Handler for Guacamole user key events. + */ +int guac_vnc_user_key_handler(guac_user* user, int keysym, int pressed); #endif diff --git a/src/protocols/vnc/log.c b/src/protocols/vnc/log.c new file mode 100644 index 00000000..9185017f --- /dev/null +++ b/src/protocols/vnc/log.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "client.h" +#include "guac_iconv.h" +#include "guac_surface.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +void guac_vnc_client_log_info(const char* format, ...) { + + char message[2048]; + + /* Copy log message into buffer */ + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + /* Log to syslog */ + syslog(LOG_INFO, "%s", message); + +} + +void guac_vnc_client_log_error(const char* format, ...) { + + char message[2048]; + + /* Copy log message into buffer */ + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + /* Log to syslog */ + syslog(LOG_ERR, "%s", message); + +} + diff --git a/src/protocols/vnc/log.h b/src/protocols/vnc/log.h new file mode 100644 index 00000000..e3ca2447 --- /dev/null +++ b/src/protocols/vnc/log.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_VNC_LOG_H +#define GUAC_VNC_LOG_H + +#include "config.h" + +#include "client.h" +#include "guac_iconv.h" +#include "guac_surface.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/** + * Log handler for informational messages from the VNC client library. + */ +void guac_vnc_client_log_info(const char* format, ...); + +/** + * Log handler for error messages from the VNC client library. + */ +void guac_vnc_client_log_error(const char* format, ...); + +#endif + diff --git a/src/protocols/vnc/pulse.c b/src/protocols/vnc/pulse.c index 05418cd6..332af7e4 100644 --- a/src/protocols/vnc/pulse.c +++ b/src/protocols/vnc/pulse.c @@ -22,8 +22,8 @@ #include "config.h" -#include "client.h" #include "pulse.h" +#include "vnc.h" #include #include @@ -66,8 +66,8 @@ static void __stream_read_callback(pa_stream* stream, size_t length, void* data) { guac_client* client = (guac_client*) data; - vnc_guac_client_data* client_data = (vnc_guac_client_data*) client->data; - guac_audio_stream* audio = client_data->audio; + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_audio_stream* audio = vnc_client->audio; const void* buffer; @@ -231,35 +231,37 @@ static void __context_state_callback(pa_context* context, void* data) { void guac_pa_start_stream(guac_client* client) { - vnc_guac_client_data* client_data = (vnc_guac_client_data*) client->data; + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_vnc_settings* settings = vnc_client->settings; + pa_context* context; guac_client_log(client, GUAC_LOG_INFO, "Starting audio stream"); /* Init main loop */ - client_data->pa_mainloop = pa_threaded_mainloop_new(); + vnc_client->pa_mainloop = pa_threaded_mainloop_new(); /* Create context */ context = pa_context_new( - pa_threaded_mainloop_get_api(client_data->pa_mainloop), + pa_threaded_mainloop_get_api(vnc_client->pa_mainloop), "Guacamole Audio"); /* Set up context */ pa_context_set_state_callback(context, __context_state_callback, client); - pa_context_connect(context, client_data->pa_servername, + pa_context_connect(context, settings->pa_servername, PA_CONTEXT_NOAUTOSPAWN, NULL); /* Start loop */ - pa_threaded_mainloop_start(client_data->pa_mainloop); + pa_threaded_mainloop_start(vnc_client->pa_mainloop); } void guac_pa_stop_stream(guac_client* client) { - vnc_guac_client_data* client_data = (vnc_guac_client_data*) client->data; + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; /* Stop loop */ - pa_threaded_mainloop_stop(client_data->pa_mainloop); + pa_threaded_mainloop_stop(vnc_client->pa_mainloop); guac_client_log(client, GUAC_LOG_INFO, "Audio stream finished"); diff --git a/src/protocols/vnc/pulse.h b/src/protocols/vnc/pulse.h index 62a1515e..4954243c 100644 --- a/src/protocols/vnc/pulse.h +++ b/src/protocols/vnc/pulse.h @@ -26,6 +26,8 @@ #include "config.h" +#include + /** * The number of bytes to request for the audio fragments received from * PulseAudio. diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c new file mode 100644 index 00000000..8afdfaa7 --- /dev/null +++ b/src/protocols/vnc/settings.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "client.h" +#include "settings.h" + +#include + +#include +#include +#include +#include + +/* Client plugin arguments */ +const char* GUAC_VNC_CLIENT_ARGS[] = { + "hostname", + "port", + "read-only", + "encodings", + "password", + "swap-red-blue", + "color-depth", + "cursor", + "autoretry", + "clipboard-encoding", + +#ifdef ENABLE_VNC_REPEATER + "dest-host", + "dest-port", +#endif + +#ifdef ENABLE_PULSE + "enable-audio", + "audio-servername", +#endif + +#ifdef ENABLE_VNC_LISTEN + "reverse-connect", + "listen-timeout", +#endif + +#ifdef ENABLE_COMMON_SSH + "enable-sftp", + "sftp-hostname", + "sftp-port", + "sftp-username", + "sftp-password", + "sftp-private-key", + "sftp-passphrase", + "sftp-directory", +#endif + + NULL +}; + +enum VNC_ARGS_IDX { + + IDX_HOSTNAME, + IDX_PORT, + IDX_READ_ONLY, + IDX_ENCODINGS, + IDX_PASSWORD, + IDX_SWAP_RED_BLUE, + IDX_COLOR_DEPTH, + IDX_CURSOR, + IDX_AUTORETRY, + IDX_CLIPBOARD_ENCODING, + +#ifdef ENABLE_VNC_REPEATER + IDX_DEST_HOST, + IDX_DEST_PORT, +#endif + +#ifdef ENABLE_PULSE + IDX_ENABLE_AUDIO, + IDX_AUDIO_SERVERNAME, +#endif + +#ifdef ENABLE_VNC_LISTEN + IDX_REVERSE_CONNECT, + IDX_LISTEN_TIMEOUT, +#endif + +#ifdef ENABLE_COMMON_SSH + IDX_ENABLE_SFTP, + IDX_SFTP_HOSTNAME, + IDX_SFTP_PORT, + IDX_SFTP_USERNAME, + IDX_SFTP_PASSWORD, + IDX_SFTP_PRIVATE_KEY, + IDX_SFTP_PASSPHRASE, + IDX_SFTP_DIRECTORY, +#endif + + VNC_ARGS_COUNT +}; + +guac_vnc_settings* guac_vnc_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != VNC_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + VNC_ARGS_COUNT, argc); + return NULL; + } + + guac_vnc_settings* settings = calloc(1, sizeof(guac_vnc_settings)); + + settings->hostname = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_HOSTNAME, ""); + + settings->port = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_PORT, 0); + + settings->password = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_PASSWORD, ""); /* NOTE: freed by libvncclient */ + + /* Remote cursor */ + if (strcmp(argv[IDX_CURSOR], "remote") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Cursor rendering: remote"); + settings->remote_cursor = true; + } + + /* Local cursor */ + else { + guac_user_log(user, GUAC_LOG_INFO, "Cursor rendering: local"); + settings->remote_cursor = false; + } + + /* Swap red/blue (for buggy VNC servers) */ + settings->swap_red_blue = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SWAP_RED_BLUE, false); + + /* Read-only mode */ + settings->read_only = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_READ_ONLY, false); + + /* Parse color depth */ + settings->color_depth = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_COLOR_DEPTH, 0); + +#ifdef ENABLE_VNC_REPEATER + /* Set repeater parameters if specified */ + settings->dest_host = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_DEST_HOST, NULL); + + /* VNC repeater port */ + settings->dest_port = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_DEST_PORT, 0); +#endif + + /* Set encodings if specified */ + settings->encodings = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_ENCODINGS, NULL); + + /* Parse autoretry */ + settings->retries = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_AUTORETRY, 0); + +#ifdef ENABLE_VNC_LISTEN + /* Set reverse-connection flag */ + settings->reverse_connect = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_REVERSE_CONNECT, false); + + /* Parse listen timeout */ + settings->listen_timeout = + guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_LISTEN_TIMEOUT, 5000); +#endif + +#ifdef ENABLE_PULSE + /* Audio enable/disable */ + settings->audio_enabled = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_ENABLE_AUDIO, false); + + /* Load servername if specified and applicable */ + if (settings->audio_enabled) + settings->pa_servername = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_AUDIO_SERVERNAME, NULL); +#endif + + /* Set clipboard encoding if specified */ + settings->clipboard_encoding = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_CLIPBOARD_ENCODING, NULL); + +#ifdef ENABLE_COMMON_SSH + /* SFTP enable/disable */ + settings->enable_sftp = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_ENABLE_SFTP, false); + + /* Hostname for SFTP connection */ + settings->sftp_hostname = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_HOSTNAME, settings->hostname); + + /* Port for SFTP connection */ + settings->sftp_port = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_PORT, "22"); + + /* Username for SSH/SFTP authentication */ + settings->sftp_username = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_USERNAME, ""); + + /* Password for SFTP (if not using private key) */ + settings->sftp_password = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_PASSWORD, ""); + + /* Private key for SFTP (if not using password) */ + settings->sftp_private_key = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_PRIVATE_KEY, NULL); + + /* Passphrase for decrypting the SFTP private key (if applicable */ + settings->sftp_passphrase = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_PASSPHRASE, ""); + + /* Default upload directory */ + settings->sftp_directory = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_SFTP_DIRECTORY, NULL); +#endif + + + return settings; + +} + +void guac_vnc_settings_free(guac_vnc_settings* settings) { + + /* Free settings strings */ + free(settings->clipboard_encoding); + free(settings->dest_host); + free(settings->encodings); + free(settings->hostname); + +#ifdef ENABLE_COMMON_SSH + /* Free SFTP settings */ + free(settings->sftp_directory); + free(settings->sftp_hostname); + free(settings->sftp_passphrase); + free(settings->sftp_password); + free(settings->sftp_port); + free(settings->sftp_private_key); + free(settings->sftp_username); +#endif + +#ifdef ENABLE_PULSE + /* Free PulseAudio settings */ + free(settings->pa_servername); +#endif + + /* Free settings structure */ + free(settings); + +} + diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h new file mode 100644 index 00000000..9d3034b5 --- /dev/null +++ b/src/protocols/vnc/settings.h @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef __GUAC_VNC_SETTINGS_H +#define __GUAC_VNC_SETTINGS_H + +#include "config.h" + +#include + +/** + * VNC-specific client data. + */ +typedef struct guac_vnc_settings { + + /** + * The hostname of the VNC server (or repeater) to connect to. + */ + char* hostname; + + /** + * The port of the VNC server (or repeater) to connect to. + */ + int port; + + /** + * The password given in the arguments. + */ + char* password; + + /** + * Space-separated list of encodings to use within the VNC session. + */ + char* encodings; + + /** + * Whether the red and blue components of each color should be swapped. + * This is mainly used for VNC servers that do not properly handle + * colors. + */ + bool swap_red_blue; + + /** + * The color depth to request, in bits. + */ + int color_depth; + + /** + * Whether this connection is read-only, and user input should be dropped. + */ + bool read_only; + + /** + * The VNC host to connect to, if using a repeater. + */ + char* dest_host; + + /** + * The VNC port to connect to, if using a repeater. + */ + int dest_port; + +#ifdef ENABLE_VNC_LISTEN + /** + * Whether not actually connecting to a VNC server, but rather listening + * for a connection from the VNC server (reverse connection). + */ + bool reverse_connect; + + /** + * The maximum amount of time to wait when listening for connections, in + * milliseconds. + */ + int listen_timeout; +#endif + + /** + * Whether the cursor should be rendered on the server (remote) or on the + * client (local). + */ + int remote_cursor; + + /** + * Whether audio is enabled. + */ + bool audio_enabled; + +#ifdef ENABLE_PULSE + /** + * The name of the PulseAudio server to connect to. + */ + char* pa_servername; +#endif + + /** + * The number of connection attempts to make before giving up. + */ + int retries; + + /** + * The encoding to use for clipboard data sent to the VNC server, or NULL + * to use the encoding required by the VNC standard. + */ + char* clipboard_encoding; + +#ifdef ENABLE_COMMON_SSH + bool enable_sftp; + + char* sftp_hostname; + + char* sftp_port; + + char* sftp_username; + + char* sftp_password; + + char* sftp_private_key; + + char* sftp_passphrase; + + char* sftp_directory; +#endif + +} guac_vnc_settings; + +/** + * Parses all given args, storing them in a newly-allocated settings object. If + * the args fail to parse, NULL is returned. + * + * @param user + * The user who submitted the given arguments while joining the + * connection. + * + * @param argc + * The number of arguments within the argv array. + * + * @param argv + * The values of all arguments provided by the user. + * + * @return + * A newly-allocated settings object which must be freed with + * guac_vnc_settings_free() when no longer needed. If the arguments fail + * to parse, NULL is returned. + */ +guac_vnc_settings* guac_vnc_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the given guac_vnc_settings object, having been previously allocated + * via guac_vnc_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_vnc_settings_free(guac_vnc_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_VNC_CLIENT_ARGS[]; + +#endif + diff --git a/src/protocols/vnc/sftp.c b/src/protocols/vnc/sftp.c index 59866330..5220f593 100644 --- a/src/protocols/vnc/sftp.c +++ b/src/protocols/vnc/sftp.c @@ -22,21 +22,23 @@ #include "config.h" -#include "client.h" #include "guac_sftp.h" #include "sftp.h" +#include "vnc.h" #include #include +#include -int guac_vnc_sftp_file_handler(guac_client* client, guac_stream* stream, +int guac_vnc_sftp_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename) { - vnc_guac_client_data* client_data = (vnc_guac_client_data*) client->data; - guac_object* filesystem = client_data->sftp_filesystem; + guac_client* client = user->client; + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_common_ssh_sftp_filesystem* filesystem = vnc_client->sftp_filesystem; /* Handle file upload */ - return guac_common_ssh_sftp_handle_file_stream(filesystem, stream, + return guac_common_ssh_sftp_handle_file_stream(filesystem, user, stream, mimetype, filename); } diff --git a/src/protocols/vnc/sftp.h b/src/protocols/vnc/sftp.h index 2477f334..8c140b4e 100644 --- a/src/protocols/vnc/sftp.h +++ b/src/protocols/vnc/sftp.h @@ -25,15 +25,15 @@ #include "config.h" -#include #include +#include /** * Handles an incoming stream from a Guacamole "file" instruction, saving the * contents of that stream to the file having the given name. * - * @param client - * The client receiving the uploaded file. + * @param user + * The user uploading the file. * * @param stream * The stream through which the uploaded file data will be received. @@ -48,7 +48,7 @@ * Zero if the incoming stream has been handled successfully, non-zero on * failure. */ -int guac_vnc_sftp_file_handler(guac_client* client, guac_stream* stream, +int guac_vnc_sftp_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename); #endif diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c new file mode 100644 index 00000000..424cf4cc --- /dev/null +++ b/src/protocols/vnc/user.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "clipboard.h" +#include "input.h" +#include "guac_display.h" +#include "guac_dot_cursor.h" +#include "guac_pointer_cursor.h" +#include "user.h" +#include "sftp.h" +#include "vnc.h" + +#include +#include +#include +#include +#include +#include + +#include + +int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + + /* Connect via VNC if owner */ + if (user->owner) { + + /* Parse arguments into client */ + guac_vnc_settings* settings = vnc_client->settings = + guac_vnc_parse_args(user, argc, (const char**) argv); + + /* Fail if settings cannot be parsed */ + if (settings == NULL) { + guac_user_log(user, GUAC_LOG_INFO, + "Badly formatted client arguments."); + return 1; + } + + /* Start client thread */ + if (pthread_create(&vnc_client->client_thread, NULL, guac_vnc_client_thread, user->client)) { + guac_user_log(user, GUAC_LOG_ERROR, "Unable to start VNC client thread."); + return 1; + } + + } + + /* If not owner, synchronize with current state */ + else { + +#ifdef ENABLE_PULSE + /* Synchronize an audio stream */ + if (vnc_client->audio) + guac_audio_stream_add_user(vnc_client->audio, user); +#endif + + /* Synchronize with current display */ + guac_common_display_dup(vnc_client->display, user, user->socket); + guac_socket_flush(user->socket); + + } + + /* Only handle events if not read-only */ + if (vnc_client->settings->read_only == 0) { + + /* General mouse/keyboard/clipboard events */ + user->mouse_handler = guac_vnc_user_mouse_handler; + user->key_handler = guac_vnc_user_key_handler; + user->clipboard_handler = guac_vnc_clipboard_handler; + +#ifdef ENABLE_COMMON_SSH + /* Set generic (non-filesystem) file upload handler */ + user->file_handler = guac_vnc_sftp_file_handler; +#endif + + } + + return 0; + +} + +int guac_vnc_user_leave_handler(guac_user* user) { + + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + + guac_common_cursor_remove_user(vnc_client->display->cursor, user); + + return 0; +} + diff --git a/src/protocols/vnc/user.h b/src/protocols/vnc/user.h new file mode 100644 index 00000000..c290bae1 --- /dev/null +++ b/src/protocols/vnc/user.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_VNC_USER_H +#define GUAC_VNC_USER_H + +#include "config.h" + +#include "guac_clipboard.h" + +#include + +/** + * Handler for joining users. + */ +int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv); + +/** + * Handler for leaving users. + */ +int guac_vnc_user_leave_handler(guac_user* user); + +#endif + diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c new file mode 100644 index 00000000..f192996b --- /dev/null +++ b/src/protocols/vnc/vnc.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "auth.h" +#include "client.h" +#include "clipboard.h" +#include "cursor.h" +#include "display.h" +#include "guac_clipboard.h" +#include "guac_cursor.h" +#include "guac_display.h" +#include "log.h" +#include "settings.h" +#include "vnc.h" + +#ifdef ENABLE_PULSE +#include "pulse.h" +#endif + +#ifdef ENABLE_COMMON_SSH +#include "guac_sftp.h" +#include "guac_ssh.h" +#include "sftp.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +char* GUAC_VNC_CLIENT_KEY = "GUAC_VNC"; + +rfbClient* guac_vnc_get_client(guac_client* client) { + + rfbClient* rfb_client = rfbGetClient(8, 3, 4); /* 32-bpp client */ + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_vnc_settings* vnc_settings = vnc_client->settings; + + /* Store Guac client in rfb client */ + rfbClientSetClientData(rfb_client, GUAC_VNC_CLIENT_KEY, client); + + /* Framebuffer update handler */ + rfb_client->GotFrameBufferUpdate = guac_vnc_update; + rfb_client->GotCopyRect = guac_vnc_copyrect; + + /* Do not handle clipboard and local cursor if read-only */ + if (vnc_settings->read_only == 0) { + + /* Clipboard */ + rfb_client->GotXCutText = guac_vnc_cut_text; + + /* Set remote cursor */ + if (vnc_settings->remote_cursor) { + rfb_client->appData.useRemoteCursor = FALSE; + } + + else { + /* Enable client-side cursor */ + rfb_client->appData.useRemoteCursor = TRUE; + rfb_client->GotCursorShape = guac_vnc_cursor; + } + + } + + /* Password */ + rfb_client->GetPassword = guac_vnc_get_password; + + /* Depth */ + guac_vnc_set_pixel_format(rfb_client, vnc_settings->color_depth); + + /* Hook into allocation so we can handle resize. */ + vnc_client->rfb_MallocFrameBuffer = rfb_client->MallocFrameBuffer; + rfb_client->MallocFrameBuffer = guac_vnc_malloc_framebuffer; + rfb_client->canHandleNewFBSize = 1; + + /* Set hostname and port */ + rfb_client->serverHost = strdup(vnc_settings->hostname); + rfb_client->serverPort = vnc_settings->port; + +#ifdef ENABLE_VNC_REPEATER + /* Set repeater parameters if specified */ + if (vnc_settings->dest_host) { + rfb_client->destHost = strdup(vnc_settings->dest_host); + rfb_client->destPort = vnc_settings->dest_port; + } +#endif + +#ifdef ENABLE_VNC_LISTEN + /* If reverse connection enabled, start listening */ + if (vnc_settings->reverse_connect) { + + guac_client_log(client, GUAC_LOG_INFO, "Listening for connections on port %i", vnc_settings->port); + + /* Listen for connection from server */ + rfb_client->listenPort = vnc_settings->port; + if (listenForIncomingConnectionsNoFork(rfb_client, vnc_settings->listen_timeout*1000) <= 0) + return NULL; + + } +#endif + + /* Set encodings if provided */ + if (vnc_settings->encodings) + rfb_client->appData.encodingsString = strdup(vnc_settings->encodings); + + /* Connect */ + if (rfbInitClient(rfb_client, NULL, NULL)) + return rfb_client; + + /* If connection fails, return NULL */ + return NULL; + +} + +/** + * Waits until data is available to be read from the given rfbClient, and thus + * a call to HandleRFBServerMessages() should not block. If the timeout elapses + * before data is available, zero is returned. + * + * @param rfb_client + * The rfbClient to wait for. + * + * @param timeout + * The maximum amount of time to wait, in microseconds. + * + * @returns + * A positive value if data is available, zero if the timeout elapses + * before data becomes available, or a negative value on error. + */ +static int guac_vnc_wait_for_messages(rfbClient* rfb_client, int timeout) { + + /* Do not explicitly wait while data is on the buffer */ + if (rfb_client->buffered) + return 1; + + /* If no data on buffer, wait for data on socket */ + return WaitForMessage(rfb_client, timeout); + +} + +void* guac_vnc_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_vnc_settings* settings = vnc_client->settings; + + /* Configure clipboard encoding */ + if (guac_vnc_set_clipboard_encoding(client, settings->clipboard_encoding)) { + guac_client_log(client, GUAC_LOG_INFO, "Using non-standard VNC " + "clipboard encoding: '%s'.", settings->clipboard_encoding); + } + + /* Ensure connection is kept alive during lengthy connects */ + guac_socket_require_keep_alive(client->socket); + + /* Set up libvncclient logging */ + rfbClientLog = guac_vnc_client_log_info; + rfbClientErr = guac_vnc_client_log_error; + + /* Attempt connection */ + rfbClient* rfb_client = guac_vnc_get_client(client); + int retries_remaining = settings->retries; + + /* If unsuccessful, retry as many times as specified */ + while (!rfb_client && retries_remaining > 0) { + + guac_client_log(client, GUAC_LOG_INFO, + "Connect failed. Waiting %ims before retrying...", + GUAC_VNC_CONNECT_INTERVAL); + + /* Wait for given interval then retry */ + guac_timestamp_msleep(GUAC_VNC_CONNECT_INTERVAL); + rfb_client = guac_vnc_get_client(client); + retries_remaining--; + + } + + /* If the final connect attempt fails, return error */ + if (!rfb_client) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to connect to VNC server."); + return NULL; + } + +#ifdef ENABLE_PULSE + /* If an encoding is available, load an audio stream */ + if (settings->audio_enabled) { + + vnc_client->audio = guac_audio_stream_alloc(client, NULL, + GUAC_VNC_AUDIO_RATE, + GUAC_VNC_AUDIO_CHANNELS, + GUAC_VNC_AUDIO_BPS); + + /* If successful, init audio system */ + if (vnc_client->audio != NULL) { + + guac_client_log(client, GUAC_LOG_INFO, + "Audio will be encoded as %s", + vnc_client->audio->encoder->mimetype); + + /* Start audio stream */ + guac_pa_start_stream(client); + + } + + /* Otherwise, audio loading failed */ + else + guac_client_log(client, GUAC_LOG_INFO, + "No available audio encoding. Sound disabled."); + + } /* end if audio enabled */ +#endif + +#ifdef ENABLE_COMMON_SSH + guac_common_ssh_init(client); + + /* Connect via SSH if SFTP is enabled */ + if (settings->enable_sftp) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Connecting via SSH for SFTP filesystem access."); + + vnc_client->sftp_user = + guac_common_ssh_create_user(settings->sftp_username); + + /* Import private key, if given */ + if (settings->sftp_private_key != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Authenticating with private key."); + + /* Abort if private key cannot be read */ + if (guac_common_ssh_user_import_key(vnc_client->sftp_user, + settings->sftp_private_key, + settings->sftp_passphrase)) { + guac_common_ssh_destroy_user(vnc_client->sftp_user); + return NULL; + } + + } + + /* Otherwise, use specified password */ + else { + guac_client_log(client, GUAC_LOG_DEBUG, + "Authenticating with password."); + guac_common_ssh_user_set_password(vnc_client->sftp_user, + settings->sftp_password); + } + + /* Attempt SSH connection */ + vnc_client->sftp_session = + guac_common_ssh_create_session(client, settings->sftp_hostname, + settings->sftp_port, vnc_client->sftp_user); + + /* Fail if SSH connection does not succeed */ + if (vnc_client->sftp_session == NULL) { + /* Already aborted within guac_common_ssh_create_session() */ + guac_common_ssh_destroy_user(vnc_client->sftp_user); + return NULL; + } + + /* Load filesystem */ + vnc_client->sftp_filesystem = + guac_common_ssh_create_sftp_filesystem( + vnc_client->sftp_session, "/"); + + /* Expose filesystem to connection owner */ + guac_client_for_owner(client, + guac_common_ssh_expose_sftp_filesystem, + vnc_client->sftp_filesystem); + + /* Abort if SFTP connection fails */ + if (vnc_client->sftp_filesystem == NULL) { + guac_common_ssh_destroy_session(vnc_client->sftp_session); + guac_common_ssh_destroy_user(vnc_client->sftp_user); + return NULL; + } + + /* Configure destination for basic uploads, if specified */ + if (settings->sftp_directory != NULL) + guac_common_ssh_sftp_set_upload_path( + vnc_client->sftp_filesystem, + settings->sftp_directory); + + guac_client_log(client, GUAC_LOG_DEBUG, + "SFTP connection succeeded."); + + } +#endif + + /* Set remaining client data */ + vnc_client->rfb_client = rfb_client; + + /* Send name */ + guac_protocol_send_name(client->socket, rfb_client->desktopName); + + /* Create display */ + vnc_client->display = guac_common_display_alloc(client, + rfb_client->width, rfb_client->height); + + /* If not read-only, set an appropriate cursor */ + if (settings->read_only == 0) { + if (settings->remote_cursor) + guac_common_cursor_set_dot(vnc_client->display->cursor); + else + guac_common_cursor_set_pointer(vnc_client->display->cursor); + + } + + guac_socket_flush(client->socket); + + guac_timestamp last_frame_end = guac_timestamp_current(); + + /* Handle messages from VNC server while client is running */ + while (client->state == GUAC_CLIENT_RUNNING) { + + /* Wait for start of frame */ + int wait_result = guac_vnc_wait_for_messages(rfb_client, 1000000); + if (wait_result > 0) { + + guac_timestamp frame_start = guac_timestamp_current(); + + /* Calculate time since last frame */ + int time_elapsed = frame_start - last_frame_end; + int processing_lag = guac_client_get_processing_lag(client); + + /* Force roughly-equal length of server and client frames */ + if (time_elapsed < processing_lag) + guac_timestamp_msleep(processing_lag - time_elapsed); + + /* Read server messages until frame is built */ + do { + + guac_timestamp frame_end; + int frame_remaining; + + /* Handle any message received */ + if (!HandleRFBServerMessage(rfb_client)) { + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Error handling message from VNC server."); + break; + } + + /* Calculate time remaining in frame */ + frame_end = guac_timestamp_current(); + frame_remaining = frame_start + GUAC_VNC_FRAME_DURATION + - frame_end; + + /* Wait again if frame remaining */ + if (frame_remaining > 0) + wait_result = guac_vnc_wait_for_messages(rfb_client, + GUAC_VNC_FRAME_TIMEOUT*1000); + else + break; + + } while (wait_result > 0); + + /* Record end of frame */ + last_frame_end = guac_timestamp_current(); + + } + + /* If an error occurs, log it and fail */ + if (wait_result < 0) + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Connection closed."); + + guac_common_surface_flush(vnc_client->display->default_surface); + guac_client_end_frame(client); + guac_socket_flush(client->socket); + + } + + guac_client_log(client, GUAC_LOG_INFO, "Internal VNC client disconnected"); + return NULL; + +} + diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h new file mode 100644 index 00000000..42e53ca7 --- /dev/null +++ b/src/protocols/vnc/vnc.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_VNC_VNC_H +#define GUAC_VNC_VNC_H + +#include "config.h" + +#include "guac_clipboard.h" +#include "guac_display.h" +#include "guac_iconv.h" +#include "guac_surface.h" +#include "settings.h" + +#include +#include +#include + +#ifdef ENABLE_PULSE +#include +#include +#endif + +#ifdef ENABLE_COMMON_SSH +#include "guac_sftp.h" +#include "guac_ssh.h" +#include "guac_ssh_user.h" +#endif + +#include + +/** + * VNC-specific client data. + */ +typedef struct guac_vnc_client { + + /** + * The VNC client thread. + */ + pthread_t client_thread; + + /** + * The underlying VNC client. + */ + rfbClient* rfb_client; + + /** + * The original framebuffer malloc procedure provided by the initialized + * rfbClient. + */ + MallocFrameBufferProc rfb_MallocFrameBuffer; + + /** + * Whether copyrect was used to produce the latest update received + * by the VNC server. + */ + int copy_rect_used; + + /** + * Client settings, parsed from args. + */ + guac_vnc_settings* settings; + + /** + * The current display state. + */ + guac_common_display* display; + + /** + * Internal clipboard. + */ + guac_common_clipboard* clipboard; + +#ifdef ENABLE_PULSE + /** + * Audio output, if any. + */ + guac_audio_stream* audio; + + /** + * PulseAudio event loop. + */ + pa_threaded_mainloop* pa_mainloop; +#endif + +#ifdef ENABLE_COMMON_SSH + /** + * The user and credentials used to authenticate for SFTP. + */ + guac_common_ssh_user* sftp_user; + + /** + * The SSH session used for SFTP. + */ + guac_common_ssh_session* sftp_session; + + /** + * An SFTP-based filesystem. + */ + guac_common_ssh_sftp_filesystem* sftp_filesystem; +#endif + + /** + * Clipboard encoding-specific reader. + */ + guac_iconv_read* clipboard_reader; + + /** + * Clipboard encoding-specific writer. + */ + guac_iconv_write* clipboard_writer; + +} guac_vnc_client; + +/** + * Allocates a new rfbClient instance given the parameters stored within the + * client, returning NULL on failure. + */ +rfbClient* guac_vnc_get_client(guac_client* client); + +/** + * VNC client thread. This thread runs throughout the duration of the client, + * existing as a single instance, shared by all users. + */ +void* guac_vnc_client_thread(void* data); + +/** + * Key which can be used with the rfbClientGetClientData function to return + * the associated guac_client. + */ +extern char* GUAC_VNC_CLIENT_KEY; + +#endif + diff --git a/src/protocols/vnc/vnc_handlers.c b/src/protocols/vnc/vnc_handlers.c deleted file mode 100644 index 7e7ccee9..00000000 --- a/src/protocols/vnc/vnc_handlers.c +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "config.h" - -#include "client.h" -#include "guac_iconv.h" -#include "guac_surface.h" - -#include -#include -#include -#include -#include -#include -#include - -/* Define cairo_format_stride_for_width() if missing */ -#ifndef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH -#define cairo_format_stride_for_width(format, width) (width*4) -#endif - -#include -#include -#include -#include - -void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp) { - - guac_client* gc = rfbClientGetClientData(client, __GUAC_CLIENT); - guac_socket* socket = gc->socket; - vnc_guac_client_data* guac_client_data = (vnc_guac_client_data*) gc->data; - const guac_layer* cursor_layer = guac_client_data->cursor; - - /* Cairo image buffer */ - int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); - unsigned char* buffer = malloc(h*stride); - unsigned char* buffer_row_current = buffer; - cairo_surface_t* surface; - - /* VNC image buffer */ - unsigned int fb_stride = bpp * w; - unsigned char* fb_row_current = client->rcSource; - unsigned char* fb_mask = client->rcMask; - - int dx, dy; - - /* Copy image data from VNC client to RGBA buffer */ - for (dy = 0; dy> client->format.redShift) * 0x100 / (client->format.redMax + 1); - green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax+ 1); - blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); - - /* Output ARGB */ - if (guac_client_data->swap_red_blue) - *(buffer_current++) = (alpha << 24) | (blue << 16) | (green << 8) | red; - else - *(buffer_current++) = (alpha << 24) | (red << 16) | (green << 8) | blue; - - /* Next VNC pixel */ - fb_current += bpp; - - } - } - - /* Send cursor data*/ - surface = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_ARGB32, w, h, stride); - - guac_client_stream_png(gc, socket, GUAC_COMP_SRC, cursor_layer, - 0, 0, surface); - - /* Update cursor */ - guac_protocol_send_cursor(socket, x, y, cursor_layer, 0, 0, w, h); - - /* Free surface */ - cairo_surface_destroy(surface); - free(buffer); - - /* libvncclient does not free rcMask as it does rcSource */ - free(client->rcMask); -} - -void guac_vnc_update(rfbClient* client, int x, int y, int w, int h) { - - guac_client* gc = rfbClientGetClientData(client, __GUAC_CLIENT); - vnc_guac_client_data* guac_client_data = (vnc_guac_client_data*) gc->data; - - int dx, dy; - - /* Cairo image buffer */ - int stride; - unsigned char* buffer; - unsigned char* buffer_row_current; - cairo_surface_t* surface; - - /* VNC framebuffer */ - unsigned int bpp; - unsigned int fb_stride; - unsigned char* fb_row_current; - - /* Ignore extra update if already handled by copyrect */ - if (guac_client_data->copy_rect_used) { - guac_client_data->copy_rect_used = 0; - return; - } - - /* Init Cairo buffer */ - stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); - buffer = malloc(h*stride); - buffer_row_current = buffer; - - bpp = client->format.bitsPerPixel/8; - fb_stride = bpp * client->width; - fb_row_current = client->frameBuffer + (y * fb_stride) + (x * bpp); - - /* Copy image data from VNC client to PNG */ - for (dy = y; dy> client->format.redShift) * 0x100 / (client->format.redMax + 1); - green = (v >> client->format.greenShift) * 0x100 / (client->format.greenMax+ 1); - blue = (v >> client->format.blueShift) * 0x100 / (client->format.blueMax + 1); - - /* Output RGB */ - if (guac_client_data->swap_red_blue) - *(buffer_current++) = (blue << 16) | (green << 8) | red; - else - *(buffer_current++) = (red << 16) | (green << 8) | blue; - - fb_current += bpp; - - } - } - - /* For now, only use default layer */ - surface = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_RGB24, w, h, stride); - - guac_common_surface_draw(guac_client_data->default_surface, x, y, surface); - - /* Free surface */ - cairo_surface_destroy(surface); - free(buffer); - -} - -void guac_vnc_copyrect(rfbClient* client, int src_x, int src_y, int w, int h, int dest_x, int dest_y) { - - guac_client* gc = rfbClientGetClientData(client, __GUAC_CLIENT); - vnc_guac_client_data* guac_client_data = (vnc_guac_client_data*) gc->data; - - /* For now, only use default layer */ - guac_common_surface_copy(guac_client_data->default_surface, src_x, src_y, w, h, - guac_client_data->default_surface, dest_x, dest_y); - - ((vnc_guac_client_data*) gc->data)->copy_rect_used = 1; - -} - -char* guac_vnc_get_password(rfbClient* client) { - guac_client* gc = rfbClientGetClientData(client, __GUAC_CLIENT); - return ((vnc_guac_client_data*) gc->data)->password; -} - -void guac_vnc_set_pixel_format(rfbClient* client, int color_depth) { - switch(color_depth) { - case 8: - client->format.depth = 8; - client->format.bitsPerPixel = 8; - client->format.blueShift = 6; - client->format.redShift = 0; - client->format.greenShift = 3; - client->format.blueMax = 3; - client->format.redMax = 7; - client->format.greenMax = 7; - break; - - case 16: - client->format.depth = 16; - client->format.bitsPerPixel = 16; - client->format.blueShift = 0; - client->format.redShift = 11; - client->format.greenShift = 5; - client->format.blueMax = 0x1f; - client->format.redMax = 0x1f; - client->format.greenMax = 0x3f; - break; - - case 24: - case 32: - default: - client->format.depth = 24; - client->format.bitsPerPixel = 32; - client->format.blueShift = 0; - client->format.redShift = 16; - client->format.greenShift = 8; - client->format.blueMax = 0xff; - client->format.redMax = 0xff; - client->format.greenMax = 0xff; - } -} - -rfbBool guac_vnc_malloc_framebuffer(rfbClient* rfb_client) { - - guac_client* gc = rfbClientGetClientData(rfb_client, __GUAC_CLIENT); - vnc_guac_client_data* guac_client_data = (vnc_guac_client_data*) gc->data; - - /* Resize surface */ - if (guac_client_data->default_surface != NULL) - guac_common_surface_resize(guac_client_data->default_surface, rfb_client->width, rfb_client->height); - - /* Use original, wrapped proc */ - return guac_client_data->rfb_MallocFrameBuffer(rfb_client); -} - -void guac_vnc_cut_text(rfbClient* client, const char* text, int textlen) { - - guac_client* gc = rfbClientGetClientData(client, __GUAC_CLIENT); - vnc_guac_client_data* client_data = (vnc_guac_client_data*) gc->data; - - char received_data[GUAC_VNC_CLIPBOARD_MAX_LENGTH]; - - const char* input = text; - char* output = received_data; - guac_iconv_read* reader = client_data->clipboard_reader; - - /* Convert clipboard contents */ - guac_iconv(reader, &input, textlen, - GUAC_WRITE_UTF8, &output, sizeof(received_data)); - - /* Send converted data */ - guac_common_clipboard_reset(client_data->clipboard, "text/plain"); - guac_common_clipboard_append(client_data->clipboard, received_data, output - received_data); - guac_common_clipboard_send(client_data->clipboard, gc); - -} - -void guac_vnc_client_log_info(const char* format, ...) { - - char message[2048]; - - /* Copy log message into buffer */ - va_list args; - va_start(args, format); - vsnprintf(message, sizeof(message), format, args); - va_end(args); - - /* Log to syslog */ - syslog(LOG_INFO, "%s", message); - -} - -void guac_vnc_client_log_error(const char* format, ...) { - - char message[2048]; - - /* Copy log message into buffer */ - va_list args; - va_start(args, format); - vsnprintf(message, sizeof(message), format, args); - va_end(args); - - /* Log to syslog */ - syslog(LOG_ERR, "%s", message); - -} -