From e573e59e83cc649e95c962d788afefa0883b2142 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 29 Feb 2016 21:50:00 -0800 Subject: [PATCH] GUAC-1389: Add screen sharing support to RDP. --- Makefile.am | 2 +- src/protocols/rdp/Makefile.am | 12 +- src/protocols/rdp/client.c | 1017 ++--------------- src/protocols/rdp/client.h | 178 +-- src/protocols/rdp/guac_handlers.c | 525 --------- .../rdp/guac_rdpdr/rdpdr_fs_service.c | 13 +- src/protocols/rdp/guac_rdpdr/rdpdr_service.c | 8 +- .../rdp/guac_rdpsnd/rdpsnd_messages.c | 28 +- src/protocols/rdp/input.c | 280 +++++ src/protocols/rdp/input.h | 102 ++ src/protocols/rdp/rdp.c | 791 +++++++++++++ src/protocols/rdp/rdp.h | 219 ++++ src/protocols/rdp/rdp_bitmap.c | 54 +- src/protocols/rdp/rdp_bitmap.h | 120 +- src/protocols/rdp/rdp_cliprdr.c | 34 +- src/protocols/rdp/rdp_cliprdr.h | 50 + src/protocols/rdp/rdp_color.c | 1 + src/protocols/rdp/rdp_color.h | 6 + src/protocols/rdp/rdp_disp.c | 2 + src/protocols/rdp/rdp_fs.c | 56 +- src/protocols/rdp/rdp_fs.h | 259 ++++- src/protocols/rdp/rdp_gdi.c | 64 +- src/protocols/rdp/rdp_gdi.h | 57 +- src/protocols/rdp/rdp_glyph.c | 15 +- src/protocols/rdp/rdp_glyph.h | 97 ++ src/protocols/rdp/rdp_keymap.h | 12 + src/protocols/rdp/rdp_pointer.c | 31 +- src/protocols/rdp/rdp_pointer.h | 45 +- src/protocols/rdp/rdp_rail.c | 9 +- src/protocols/rdp/rdp_rail.h | 9 +- src/protocols/rdp/rdp_settings.c | 464 ++++++++ src/protocols/rdp/rdp_settings.h | 96 ++ src/protocols/rdp/rdp_stream.c | 183 +-- src/protocols/rdp/rdp_stream.h | 91 +- src/protocols/rdp/rdp_svc.c | 27 +- src/protocols/rdp/rdp_svc.h | 30 + src/protocols/rdp/resolution.c | 28 +- src/protocols/rdp/resolution.h | 16 +- src/protocols/rdp/sftp.c | 12 +- src/protocols/rdp/sftp.h | 8 +- src/protocols/rdp/unicode.h | 16 +- src/protocols/rdp/user.c | 123 ++ src/protocols/rdp/{guac_handlers.h => user.h} | 38 +- 43 files changed, 3269 insertions(+), 1959 deletions(-) delete mode 100644 src/protocols/rdp/guac_handlers.c create mode 100644 src/protocols/rdp/input.c create mode 100644 src/protocols/rdp/input.h create mode 100644 src/protocols/rdp/rdp.c create mode 100644 src/protocols/rdp/rdp.h create mode 100644 src/protocols/rdp/user.c rename src/protocols/rdp/{guac_handlers.h => user.h} (55%) diff --git a/Makefile.am b/Makefile.am index 543cd563..1836cffa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -50,7 +50,7 @@ if ENABLE_TERMINAL endif if ENABLE_RDP -#SUBDIRS += src/protocols/rdp +SUBDIRS += src/protocols/rdp endif if ENABLE_SSH diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 8006f026..69492a16 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -28,7 +28,8 @@ lib_LTLIBRARIES = libguac-client-rdp.la libguac_client_rdp_la_SOURCES = \ _generated_keymaps.c \ client.c \ - guac_handlers.c \ + input.c \ + rdp.c \ rdp_bitmap.c \ rdp_cliprdr.c \ rdp_color.c \ @@ -42,7 +43,8 @@ libguac_client_rdp_la_SOURCES = \ rdp_stream.c \ rdp_svc.c \ resolution.c \ - unicode.c + unicode.c \ + user.c guacsvc_sources = \ guac_svc/svc_service.c \ @@ -80,7 +82,8 @@ noinst_HEADERS = \ guac_rdpsnd/rdpsnd_service.h \ guac_svc/svc_service.h \ client.h \ - guac_handlers.h \ + input.h \ + rdp.h \ rdp_bitmap.h \ rdp_cliprdr.h \ rdp_color.h \ @@ -95,7 +98,8 @@ noinst_HEADERS = \ rdp_stream.h \ rdp_svc.h \ resolution.h \ - unicode.h + unicode.h \ + user.h # Add compatibility layer for WinPR if not available if ! ENABLE_WINPR diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 239f4aeb..65f28620 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -23,39 +23,24 @@ #include "config.h" #include "client.h" -#include "guac_handlers.h" -#include "guac_pointer_cursor.h" -#include "guac_string.h" -#include "rdp_bitmap.h" -#include "rdp_gdi.h" -#include "rdp_glyph.h" +#include "rdp.h" #include "rdp_keymap.h" -#include "rdp_pointer.h" -#include "rdp_stream.h" -#include "rdp_svc.h" -#include "resolution.h" +#include "user.h" #ifdef ENABLE_COMMON_SSH -#include "guac_sftp.h" -#include "guac_ssh.h" -#include "sftp.h" +#include +#include +#include #endif #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT #include "rdp_disp.h" #endif -#include -#include -#include -#include -#include -#include +#include #include #include -#include #include -#include #include #ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H @@ -64,942 +49,102 @@ #include "compat/client-cliprdr.h" #endif -#ifdef HAVE_FREERDP_CLIENT_DISP_H -#include -#endif - -#ifdef HAVE_FREERDP_EVENT_PUBSUB -#include -#endif - -#ifdef ENABLE_WINPR -#include -#else -#include "compat/winpr-wtypes.h" -#endif - -#ifdef HAVE_FREERDP_ADDIN_H -#include -#endif - #ifdef HAVE_FREERDP_CLIENT_CHANNELS_H #include #endif -#ifdef HAVE_FREERDP_VERSION_H -#include -#endif - #include #include #include -#include - -/* Client plugin arguments */ -const char* GUAC_CLIENT_ARGS[] = { - "hostname", - "port", - "domain", - "username", - "password", - "width", - "height", - "dpi", - "initial-program", - "color-depth", - "disable-audio", - "enable-printing", - "enable-drive", - "drive-path", - "create-drive-path", - "console", - "console-audio", - "server-layout", - "security", - "ignore-cert", - "disable-auth", - "remote-app", - "remote-app-dir", - "remote-app-args", - "static-channels", - "client-name", - "enable-wallpaper", - "enable-theming", - "enable-font-smoothing", - "enable-full-window-drag", - "enable-desktop-composition", - "enable-menu-animations", - "preconnection-id", - "preconnection-blob", - -#ifdef ENABLE_COMMON_SSH - "enable-sftp", - "sftp-hostname", - "sftp-port", - "sftp-username", - "sftp-password", - "sftp-private-key", - "sftp-passphrase", - "sftp-directory", -#endif - - NULL -}; - -enum RDP_ARGS_IDX { - - IDX_HOSTNAME, - IDX_PORT, - IDX_DOMAIN, - IDX_USERNAME, - IDX_PASSWORD, - IDX_WIDTH, - IDX_HEIGHT, - IDX_DPI, - IDX_INITIAL_PROGRAM, - IDX_COLOR_DEPTH, - IDX_DISABLE_AUDIO, - IDX_ENABLE_PRINTING, - IDX_ENABLE_DRIVE, - IDX_DRIVE_PATH, - IDX_CREATE_DRIVE_PATH, - IDX_CONSOLE, - IDX_CONSOLE_AUDIO, - IDX_SERVER_LAYOUT, - IDX_SECURITY, - IDX_IGNORE_CERT, - IDX_DISABLE_AUTH, - IDX_REMOTE_APP, - IDX_REMOTE_APP_DIR, - IDX_REMOTE_APP_ARGS, - IDX_STATIC_CHANNELS, - IDX_CLIENT_NAME, - IDX_ENABLE_WALLPAPER, - IDX_ENABLE_THEMING, - IDX_ENABLE_FONT_SMOOTHING, - IDX_ENABLE_FULL_WINDOW_DRAG, - IDX_ENABLE_DESKTOP_COMPOSITION, - IDX_ENABLE_MENU_ANIMATIONS, - IDX_PRECONNECTION_ID, - IDX_PRECONNECTION_BLOB, - -#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 - - RDP_ARGS_COUNT -}; - -#if defined(FREERDP_VERSION_MAJOR) && (FREERDP_VERSION_MAJOR > 1 || FREERDP_VERSION_MINOR >= 2) -int __guac_receive_channel_data(freerdp* rdp_inst, UINT16 channelId, BYTE* data, int size, int flags, int total_size) { -#else -int __guac_receive_channel_data(freerdp* rdp_inst, int channelId, UINT8* data, int size, int flags, int total_size) { -#endif - return freerdp_channels_data(rdp_inst, channelId, data, size, flags, total_size); -} - -#ifdef HAVE_FREERDP_EVENT_PUBSUB -/** - * Called whenever a channel connects via the PubSub event system within - * FreeRDP. - * - * @param context The rdpContext associated with the active RDP session. - * @param e Event-specific arguments, mainly the name of the channel, and a - * reference to the associated plugin loaded for that channel by - * FreeRDP. - */ -static void guac_rdp_channel_connected(rdpContext* context, - ChannelConnectedEventArgs* e) { - -#ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL - /* Store reference to the display update plugin once it's connected */ - if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) { - - DispClientContext* disp = (DispClientContext*) e->pInterface; - - guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; - - /* Init module with current display size */ - guac_rdp_disp_set_size(guac_client_data->disp, context, - guac_rdp_get_width(context->instance), - guac_rdp_get_height(context->instance)); - - /* Store connected channel */ - guac_rdp_disp_connect(guac_client_data->disp, disp); - guac_client_log(client, GUAC_LOG_DEBUG, - "Display update channel connected."); - - } -#endif - -} -#endif - -BOOL rdp_freerdp_pre_connect(freerdp* instance) { - - rdpContext* context = instance->context; - guac_client* client = ((rdp_freerdp_context*) context)->client; - rdpChannels* channels = context->channels; - rdpBitmap* bitmap; - rdpGlyph* glyph; - rdpPointer* pointer; - rdpPrimaryUpdate* primary; - CLRCONV* clrconv; - - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; - -#ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER - /* Init FreeRDP add-in provider */ - freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); -#endif - -#ifdef HAVE_FREERDP_EVENT_PUBSUB - /* Subscribe to and handle channel connected events */ - PubSub_SubscribeChannelConnected(context->pubSub, - (pChannelConnectedEventHandler) guac_rdp_channel_connected); -#endif - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /* Load virtual channel management plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "drdynvc", instance->settings)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load drdynvc plugin."); - - /* Init display update plugin */ - guac_client_data->disp = guac_rdp_disp_alloc(); - guac_rdp_disp_load_plugin(instance->context); -#endif - - /* Load clipboard plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "cliprdr", NULL)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load cliprdr plugin. Clipboard will not work."); - - /* If audio enabled, choose an encoder */ - if (guac_client_data->settings.audio_enabled) { - - guac_client_data->audio = guac_audio_stream_alloc(client, NULL, - GUAC_RDP_AUDIO_RATE, - GUAC_RDP_AUDIO_CHANNELS, - GUAC_RDP_AUDIO_BPS); - - /* Warn if no audio encoding is available */ - if (guac_client_data->audio == NULL) - guac_client_log(client, GUAC_LOG_INFO, - "No available audio encoding. Sound disabled."); - - } /* end if audio enabled */ - - /* Load filesystem if drive enabled */ - if (guac_client_data->settings.drive_enabled) { - - /* Allocate filesystem */ - guac_client_data->filesystem = - guac_rdp_fs_alloc(client, guac_client_data->settings.drive_path, - guac_client_data->settings.create_drive_path); - - /* Use for basic uploads if no other handler set */ - if (client->file_handler == NULL) - client->file_handler = guac_rdp_upload_file_handler; - - } - - /* If RDPSND/RDPDR required, load them */ - if (guac_client_data->settings.printing_enabled - || guac_client_data->settings.drive_enabled - || guac_client_data->settings.audio_enabled) { - - /* Load RDPDR plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "guacdr", client)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load guacdr plugin. Drive redirection and " - "printing will not work. Sound MAY not work."); - - /* Load RDPSND plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "guacsnd", client)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load guacsnd alongside guacdr plugin. Sound " - "will not work. Drive redirection and printing MAY not " - "work."); - - } - - /* Load RAIL plugin if RemoteApp in use */ - if (guac_client_data->settings.remote_app != NULL) { - -#ifdef LEGACY_FREERDP - RDP_PLUGIN_DATA* plugin_data = malloc(sizeof(RDP_PLUGIN_DATA) * 2); - - plugin_data[0].size = sizeof(RDP_PLUGIN_DATA); - plugin_data[0].data[0] = guac_client_data->settings.remote_app; - plugin_data[0].data[1] = guac_client_data->settings.remote_app_dir; - plugin_data[0].data[2] = guac_client_data->settings.remote_app_args; - plugin_data[0].data[3] = NULL; - - plugin_data[1].size = 0; - - /* Attempt to load rail */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "rail", plugin_data)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load rail plugin. RemoteApp will not work."); -#else - /* Attempt to load rail */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "rail", instance->settings)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load rail plugin. RemoteApp will not work."); -#endif - - } - - /* Load SVC plugin instances for all static channels */ - if (guac_client_data->settings.svc_names != NULL) { - - char** current = guac_client_data->settings.svc_names; - do { - - guac_rdp_svc* svc = guac_rdp_alloc_svc(client, *current); - - /* Attempt to load guacsvc plugin for new static channel */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "guacsvc", svc)) { - guac_client_log(client, GUAC_LOG_WARNING, - "Cannot create static channel \"%s\": failed to load guacsvc plugin.", - svc->name); - guac_rdp_free_svc(svc); - } - - /* Store and log on success */ - else { - guac_rdp_add_svc(client, svc); - guac_client_log(client, GUAC_LOG_INFO, "Created static channel \"%s\"...", - svc->name); - } - - } while (*(++current) != NULL); - - } - - /* Init color conversion structure */ - clrconv = calloc(1, sizeof(CLRCONV)); - clrconv->alpha = 1; - clrconv->invert = 0; - clrconv->rgb555 = 0; - clrconv->palette = calloc(1, sizeof(rdpPalette)); - ((rdp_freerdp_context*) context)->clrconv = clrconv; - - /* Init FreeRDP cache */ - instance->context->cache = cache_new(instance->settings); - - /* Set up bitmap handling */ - bitmap = calloc(1, sizeof(rdpBitmap)); - bitmap->size = sizeof(guac_rdp_bitmap); - bitmap->New = guac_rdp_bitmap_new; - bitmap->Free = guac_rdp_bitmap_free; - bitmap->Paint = guac_rdp_bitmap_paint; - bitmap->Decompress = guac_rdp_bitmap_decompress; - bitmap->SetSurface = guac_rdp_bitmap_setsurface; - graphics_register_bitmap(context->graphics, bitmap); - free(bitmap); - - /* Set up glyph handling */ - glyph = calloc(1, sizeof(rdpGlyph)); - glyph->size = sizeof(guac_rdp_glyph); - glyph->New = guac_rdp_glyph_new; - glyph->Free = guac_rdp_glyph_free; - glyph->Draw = guac_rdp_glyph_draw; - glyph->BeginDraw = guac_rdp_glyph_begindraw; - glyph->EndDraw = guac_rdp_glyph_enddraw; - graphics_register_glyph(context->graphics, glyph); - free(glyph); - - /* Set up pointer handling */ - pointer = calloc(1, sizeof(rdpPointer)); - pointer->size = sizeof(guac_rdp_pointer); - pointer->New = guac_rdp_pointer_new; - pointer->Free = guac_rdp_pointer_free; - pointer->Set = guac_rdp_pointer_set; -#ifdef HAVE_RDPPOINTER_SETNULL - pointer->SetNull = guac_rdp_pointer_set_null; -#endif -#ifdef HAVE_RDPPOINTER_SETDEFAULT - pointer->SetDefault = guac_rdp_pointer_set_default; -#endif - graphics_register_pointer(context->graphics, pointer); - free(pointer); - - /* Set up GDI */ - instance->update->DesktopResize = guac_rdp_gdi_desktop_resize; - instance->update->EndPaint = guac_rdp_gdi_end_paint; - instance->update->Palette = guac_rdp_gdi_palette_update; - instance->update->SetBounds = guac_rdp_gdi_set_bounds; - - primary = instance->update->primary; - primary->DstBlt = guac_rdp_gdi_dstblt; - primary->PatBlt = guac_rdp_gdi_patblt; - primary->ScrBlt = guac_rdp_gdi_scrblt; - primary->MemBlt = guac_rdp_gdi_memblt; - primary->OpaqueRect = guac_rdp_gdi_opaquerect; - - pointer_cache_register_callbacks(instance->update); - glyph_cache_register_callbacks(instance->update); - brush_cache_register_callbacks(instance->update); - bitmap_cache_register_callbacks(instance->update); - offscreen_cache_register_callbacks(instance->update); - palette_cache_register_callbacks(instance->update); - - /* Init channels (pre-connect) */ - if (freerdp_channels_pre_connect(channels, instance)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager"); - return FALSE; - } - - return TRUE; - -} - -BOOL rdp_freerdp_post_connect(freerdp* instance) { - - rdpContext* context = instance->context; - guac_client* client = ((rdp_freerdp_context*) context)->client; - rdpChannels* channels = instance->context->channels; - - /* Init channels (post-connect) */ - if (freerdp_channels_post_connect(channels, instance)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager"); - return FALSE; - } - - /* Client handlers */ - client->free_handler = rdp_guac_client_free_handler; - client->handle_messages = rdp_guac_client_handle_messages; - client->mouse_handler = rdp_guac_client_mouse_handler; - client->key_handler = rdp_guac_client_key_handler; - client->size_handler = rdp_guac_client_size_handler; - - /* Stream handlers */ - client->clipboard_handler = guac_rdp_clipboard_handler; - client->pipe_handler = guac_rdp_svc_pipe_handler; - - return TRUE; - -} - -BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, - char** password, char** domain) { - - rdpContext* context = instance->context; - guac_client* client = ((rdp_freerdp_context*) context)->client; - - /* Warn if connection is likely to fail due to lack of credentials */ - guac_client_log(client, GUAC_LOG_INFO, - "Authentication requested but username or password not given"); - return TRUE; - -} - -BOOL rdp_freerdp_verify_certificate(freerdp* instance, char* subject, - char* issuer, char* fingerprint) { - - rdpContext* context = instance->context; - guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; - - /* Bypass validation if ignore_certificate given */ - if (guac_client_data->settings.ignore_certificate) { - guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed"); - return TRUE; - } - - guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed"); - return FALSE; - -} - -void rdp_freerdp_context_new(freerdp* instance, rdpContext* context) { - context->channels = freerdp_channels_new(); -} - -void rdp_freerdp_context_free(freerdp* instance, rdpContext* context) { - /* EMPTY */ -} - -void __guac_rdp_client_load_keymap(guac_client* client, - const guac_rdp_keymap* keymap) { - - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; - - /* Get mapping */ - const guac_rdp_keysym_desc* mapping = keymap->mapping; - - /* If parent exists, load parent first */ - if (keymap->parent != NULL) - __guac_rdp_client_load_keymap(client, keymap->parent); - - /* Log load */ - guac_client_log(client, GUAC_LOG_INFO, "Loading keymap \"%s\"", keymap->name); - - /* Load mapping into keymap */ - while (mapping->keysym != 0) { - - /* Copy mapping */ - GUAC_RDP_KEYSYM_LOOKUP(guac_client_data->keymap, mapping->keysym) = - *mapping; - - /* Next keysym */ - mapping++; - - } - -} int guac_client_init(guac_client* client, int argc, char** argv) { - rdp_guac_client_data* guac_client_data; - guac_rdp_settings* settings; + /* Set client args */ + client->args = GUAC_RDP_CLIENT_ARGS; - freerdp* rdp_inst; + /* Alloc client data */ + guac_rdp_client* rdp_client = calloc(1, sizeof(guac_rdp_client)); + client->data = rdp_client; - /* Validate number of arguments received */ - if (argc != RDP_ARGS_COUNT) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Wrong argument count received."); - return 1; - } - - /* Allocate client data */ - guac_client_data = malloc(sizeof(rdp_guac_client_data)); - - /* Init random number generator */ - srandom(time(NULL)); - - /* Init client */ -#ifdef HAVE_FREERDP_CHANNELS_GLOBAL_INIT - freerdp_channels_global_init(); -#endif - rdp_inst = freerdp_new(); - rdp_inst->PreConnect = rdp_freerdp_pre_connect; - rdp_inst->PostConnect = rdp_freerdp_post_connect; - rdp_inst->Authenticate = rdp_freerdp_authenticate; - rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate; - rdp_inst->ReceiveChannelData = __guac_receive_channel_data; - - /* Allocate FreeRDP context */ -#ifdef LEGACY_FREERDP - rdp_inst->context_size = sizeof(rdp_freerdp_context); -#else - rdp_inst->ContextSize = sizeof(rdp_freerdp_context); -#endif - rdp_inst->ContextNew = (pContextNew) rdp_freerdp_context_new; - rdp_inst->ContextFree = (pContextFree) rdp_freerdp_context_free; - freerdp_context_new(rdp_inst); - - /* Set settings */ - settings = &(guac_client_data->settings); - - /* Console */ - settings->console = (strcmp(argv[IDX_CONSOLE], "true") == 0); - settings->console_audio = (strcmp(argv[IDX_CONSOLE_AUDIO], "true") == 0); - - /* Certificate and auth */ - settings->ignore_certificate = (strcmp(argv[IDX_IGNORE_CERT], "true") == 0); - settings->disable_authentication = (strcmp(argv[IDX_DISABLE_AUTH], "true") == 0); - - /* NLA security */ - if (strcmp(argv[IDX_SECURITY], "nla") == 0) { - guac_client_log(client, GUAC_LOG_INFO, "Security mode: NLA"); - settings->security_mode = GUAC_SECURITY_NLA; - } - - /* TLS security */ - else if (strcmp(argv[IDX_SECURITY], "tls") == 0) { - guac_client_log(client, GUAC_LOG_INFO, "Security mode: TLS"); - settings->security_mode = GUAC_SECURITY_TLS; - } - - /* RDP security */ - else if (strcmp(argv[IDX_SECURITY], "rdp") == 0) { - guac_client_log(client, GUAC_LOG_INFO, "Security mode: RDP"); - settings->security_mode = GUAC_SECURITY_RDP; - } - - /* ANY security (allow server to choose) */ - else if (strcmp(argv[IDX_SECURITY], "any") == 0) { - guac_client_log(client, GUAC_LOG_INFO, "Security mode: ANY"); - settings->security_mode = GUAC_SECURITY_ANY; - } - - /* If nothing given, default to RDP */ - else { - guac_client_log(client, GUAC_LOG_INFO, "No security mode specified. Defaulting to RDP."); - settings->security_mode = GUAC_SECURITY_RDP; - } - - /* Set hostname */ - settings->hostname = strdup(argv[IDX_HOSTNAME]); - - /* If port specified, use it */ - settings->port = RDP_DEFAULT_PORT; - if (argv[IDX_PORT][0] != '\0') - settings->port = atoi(argv[IDX_PORT]); - - guac_client_log(client, GUAC_LOG_DEBUG, - "Client resolution is %ix%i at %i DPI", - client->info.optimal_width, - client->info.optimal_height, - client->info.optimal_resolution); - - /* Use suggested resolution unless overridden */ - settings->resolution = guac_rdp_suggest_resolution(client); - if (argv[IDX_DPI][0] != '\0') - settings->resolution = atoi(argv[IDX_DPI]); - - /* Use optimal width unless overridden */ - settings->width = client->info.optimal_width - * settings->resolution - / client->info.optimal_resolution; - - if (argv[IDX_WIDTH][0] != '\0') - settings->width = atoi(argv[IDX_WIDTH]); - - /* Use default width if given width is invalid. */ - if (settings->width <= 0) { - settings->width = RDP_DEFAULT_WIDTH; - guac_client_log(client, GUAC_LOG_ERROR, - "Invalid width: \"%s\". Using default of %i.", - argv[IDX_WIDTH], settings->width); - } - - /* Round width down to nearest multiple of 4 */ - settings->width = settings->width & ~0x3; - - /* Use optimal height unless overridden */ - settings->height = client->info.optimal_height - * settings->resolution - / client->info.optimal_resolution; - - if (argv[IDX_HEIGHT][0] != '\0') - settings->height = atoi(argv[IDX_HEIGHT]); - - /* Use default height if given height is invalid. */ - if (settings->height <= 0) { - settings->height = RDP_DEFAULT_HEIGHT; - guac_client_log(client, GUAC_LOG_ERROR, - "Invalid height: \"%s\". Using default of %i.", - argv[IDX_WIDTH], settings->height); - } - - guac_client_log(client, GUAC_LOG_DEBUG, - "Using resolution of %ix%i at %i DPI", - settings->width, - settings->height, - settings->resolution); - - /* Domain */ - settings->domain = NULL; - if (argv[IDX_DOMAIN][0] != '\0') - settings->domain = strdup(argv[IDX_DOMAIN]); - - /* Username */ - settings->username = NULL; - if (argv[IDX_USERNAME][0] != '\0') - settings->username = strdup(argv[IDX_USERNAME]); - - /* Password */ - settings->password = NULL; - if (argv[IDX_PASSWORD][0] != '\0') - settings->password = strdup(argv[IDX_PASSWORD]); - - /* Client name */ - settings->client_name = NULL; - if (argv[IDX_CLIENT_NAME][0] != '\0') - settings->client_name = strdup(argv[IDX_CLIENT_NAME]); - - /* Initial program */ - settings->initial_program = NULL; - if (argv[IDX_INITIAL_PROGRAM][0] != '\0') - settings->initial_program = strdup(argv[IDX_INITIAL_PROGRAM]); - - /* RemoteApp program */ - settings->remote_app = NULL; - if (argv[IDX_REMOTE_APP][0] != '\0') - settings->remote_app = strdup(argv[IDX_REMOTE_APP]); - - /* RemoteApp working directory */ - settings->remote_app_dir = NULL; - if (argv[IDX_REMOTE_APP_DIR][0] != '\0') - settings->remote_app_dir = strdup(argv[IDX_REMOTE_APP_DIR]); - - /* RemoteApp arguments */ - settings->remote_app_args = NULL; - if (argv[IDX_REMOTE_APP_ARGS][0] != '\0') - settings->remote_app_args = strdup(argv[IDX_REMOTE_APP_ARGS]); - - /* Static virtual channels */ - settings->svc_names = NULL; - if (argv[IDX_STATIC_CHANNELS][0] != '\0') - settings->svc_names = guac_split(argv[IDX_STATIC_CHANNELS], ','); - - /* Performance flags */ - settings->wallpaper_enabled = (strcmp(argv[IDX_ENABLE_WALLPAPER], "true") == 0); - settings->theming_enabled = (strcmp(argv[IDX_ENABLE_THEMING], "true") == 0); - settings->font_smoothing_enabled = (strcmp(argv[IDX_ENABLE_FONT_SMOOTHING], "true") == 0); - settings->full_window_drag_enabled = (strcmp(argv[IDX_ENABLE_FULL_WINDOW_DRAG], "true") == 0); - settings->desktop_composition_enabled = (strcmp(argv[IDX_ENABLE_DESKTOP_COMPOSITION], "true") == 0); - settings->menu_animations_enabled = (strcmp(argv[IDX_ENABLE_MENU_ANIMATIONS], "true") == 0); - - /* Session color depth */ - settings->color_depth = RDP_DEFAULT_DEPTH; - if (argv[IDX_COLOR_DEPTH][0] != '\0') - settings->color_depth = atoi(argv[IDX_COLOR_DEPTH]); - - /* Use default depth if given depth is invalid. */ - if (settings->color_depth == 0) { - settings->color_depth = RDP_DEFAULT_DEPTH; - guac_client_log(client, GUAC_LOG_ERROR, - "Invalid color-depth: \"%s\". Using default of %i.", - argv[IDX_WIDTH], settings->color_depth); - } - - /* Preconnection ID */ - settings->preconnection_id = -1; - if (argv[IDX_PRECONNECTION_ID][0] != '\0') { - - /* Parse preconnection ID, warn if invalid */ - int preconnection_id = atoi(argv[IDX_PRECONNECTION_ID]); - if (preconnection_id < 0) - guac_client_log(client, GUAC_LOG_WARNING, - "Ignoring invalid preconnection ID: %i", - preconnection_id); - - /* Otherwise, assign specified ID */ - else { - settings->preconnection_id = preconnection_id; - guac_client_log(client, GUAC_LOG_DEBUG, - "Preconnection ID: %i", settings->preconnection_id); - } - - } - - /* Preconnection BLOB */ - settings->preconnection_blob = NULL; - if (argv[IDX_PRECONNECTION_BLOB][0] != '\0') { - settings->preconnection_blob = strdup(argv[IDX_PRECONNECTION_BLOB]); - guac_client_log(client, GUAC_LOG_DEBUG, - "Preconnection BLOB: \"%s\"", settings->preconnection_blob); - } - -#ifndef HAVE_RDPSETTINGS_SENDPRECONNECTIONPDU - /* Warn if support for the preconnection BLOB / ID is absent */ - if (settings->preconnection_blob != NULL - || settings->preconnection_id != -1) { - guac_client_log(client, GUAC_LOG_WARNING, - "Installed version of FreeRDP lacks support for the " - "preconnection PDU. The specified preconnection BLOB and/or " - "ID will be ignored."); - } -#endif - - /* Audio enable/disable */ - guac_client_data->settings.audio_enabled = - (strcmp(argv[IDX_DISABLE_AUDIO], "true") != 0); - - /* Printing enable/disable */ - guac_client_data->settings.printing_enabled = - (strcmp(argv[IDX_ENABLE_PRINTING], "true") == 0); - - /* Drive enable/disable */ - guac_client_data->settings.drive_enabled = - (strcmp(argv[IDX_ENABLE_DRIVE], "true") == 0); - - guac_client_data->settings.drive_path = strdup(argv[IDX_DRIVE_PATH]); - - guac_client_data->settings.create_drive_path = - (strcmp(argv[IDX_CREATE_DRIVE_PATH], "true") == 0); - - /* Store client data */ - guac_client_data->rdp_inst = rdp_inst; - guac_client_data->mouse_button_mask = 0; - guac_client_data->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); - guac_client_data->requested_clipboard_format = CB_FORMAT_TEXT; - guac_client_data->audio = NULL; - guac_client_data->filesystem = NULL; - guac_client_data->available_svc = guac_common_list_alloc(); - - /* Main socket needs to be threadsafe */ - guac_socket_require_threadsafe(client->socket); + /* Init clipboard and shared mouse */ + rdp_client->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); + rdp_client->requested_clipboard_format = CB_FORMAT_TEXT; + rdp_client->available_svc = guac_common_list_alloc(); /* Recursive attribute for locks */ - pthread_mutexattr_init(&(guac_client_data->attributes)); - pthread_mutexattr_settype(&(guac_client_data->attributes), + pthread_mutexattr_init(&(rdp_client->attributes)); + pthread_mutexattr_settype(&(rdp_client->attributes), PTHREAD_MUTEX_RECURSIVE); /* Init RDP lock */ - pthread_mutex_init(&(guac_client_data->rdp_lock), - &(guac_client_data->attributes)); + pthread_mutex_init(&(rdp_client->rdp_lock), &(rdp_client->attributes)); /* Clear keysym state mapping and keymap */ - memset(guac_client_data->keysym_state, 0, - sizeof(guac_rdp_keysym_state_map)); + memset(rdp_client->keysym_state, 0, sizeof(guac_rdp_keysym_state_map)); + memset(rdp_client->keymap, 0, sizeof(guac_rdp_static_keymap)); - memset(guac_client_data->keymap, 0, - sizeof(guac_rdp_static_keymap)); + /* Set handlers */ + client->join_handler = guac_rdp_user_join_handler; + client->free_handler = guac_rdp_client_free_handler; + + return 0; + +} + +int guac_rdp_client_free_handler(guac_client* client) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + freerdp* rdp_inst = rdp_client->rdp_inst; + if (rdp_inst != NULL) { + rdpChannels* channels = rdp_inst->context->channels; + + /* Clean up RDP client */ + freerdp_channels_close(channels, rdp_inst); + freerdp_channels_free(channels); + freerdp_disconnect(rdp_inst); + freerdp_clrconv_free(((rdp_freerdp_context*) rdp_inst->context)->clrconv); + cache_free(rdp_inst->context->cache); + freerdp_free(rdp_inst); + } + + /* Clean up filesystem, if allocated */ + if (rdp_client->filesystem != NULL) + guac_rdp_fs_free(rdp_client->filesystem); + +#ifdef ENABLE_COMMON_SSH + /* Free SFTP filesystem, if loaded */ + if (rdp_client->sftp_filesystem) + guac_common_ssh_destroy_sftp_filesystem(rdp_client->sftp_filesystem); + + /* Free SFTP session */ + if (rdp_client->sftp_session) + guac_common_ssh_destroy_session(rdp_client->sftp_session); + + /* Free SFTP user */ + if (rdp_client->sftp_user) + guac_common_ssh_destroy_user(rdp_client->sftp_user); + + guac_common_ssh_uninit(); +#endif + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + /* Free display update module */ + guac_rdp_disp_free(rdp_client->disp); +#endif + + /* Free SVC list */ + guac_common_list_free(rdp_client->available_svc); + + /* Free parsed settings */ + if (rdp_client->settings != NULL) + guac_rdp_settings_free(rdp_client->settings); + + /* Free client data */ + guac_common_clipboard_free(rdp_client->clipboard); + guac_common_display_free(rdp_client->display); + free(rdp_client); - client->data = guac_client_data; - ((rdp_freerdp_context*) rdp_inst->context)->client = client; - - /* Pick keymap based on argument */ - settings->server_layout = NULL; - if (argv[IDX_SERVER_LAYOUT][0] != '\0') - settings->server_layout = - guac_rdp_keymap_find(argv[IDX_SERVER_LAYOUT]); - - /* If no keymap requested, use default */ - if (settings->server_layout == NULL) - settings->server_layout = guac_rdp_keymap_find(GUAC_DEFAULT_KEYMAP); - - /* Load keymap into client */ - __guac_rdp_client_load_keymap(client, settings->server_layout); - -#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."); - - /* Parse username - use RDP username by default */ - const char* sftp_username = argv[IDX_SFTP_USERNAME]; - if (sftp_username[0] == '\0' && settings->username != NULL) - sftp_username = settings->username; - - guac_client_data->sftp_user = - guac_common_ssh_create_user(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."); - - /* Parse password - use RDP password by default */ - const char* sftp_password = argv[IDX_SFTP_PASSWORD]; - if (sftp_password[0] == '\0' && settings->password != NULL) - sftp_password = settings->password; - - guac_common_ssh_user_set_password(guac_client_data->sftp_user, - sftp_password); - - } - - /* Parse hostname - use RDP hostname by default */ - const char* sftp_hostname = argv[IDX_SFTP_HOSTNAME]; - if (sftp_hostname[0] == '\0') - sftp_hostname = settings->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') { - client->file_handler = guac_rdp_sftp_file_handler; - guac_common_ssh_sftp_set_upload_path( - guac_client_data->sftp_filesystem, - argv[IDX_SFTP_DIRECTORY]); - } - - /* Otherwise, use SFTP for basic uploads only if drive not enabled */ - else if (!settings->drive_enabled) - client->file_handler = guac_rdp_sftp_file_handler; - - guac_client_log(client, GUAC_LOG_DEBUG, - "SFTP connection succeeded."); - - } -#endif - - /* Create default surface */ - guac_client_data->default_surface = guac_common_surface_alloc(client, - client->socket, GUAC_DEFAULT_LAYER, - settings->width, settings->height); - guac_client_data->current_surface = guac_client_data->default_surface; - - /* Send connection name */ - guac_protocol_send_name(client->socket, settings->hostname); - - /* Set default pointer */ - guac_common_set_pointer_cursor(client); - - /* Push desired settings to FreeRDP */ - guac_rdp_push_settings(settings, rdp_inst); - - /* Connect to RDP server */ - if (!freerdp_connect(rdp_inst)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Error connecting to RDP server"); - return 1; - } - - /* Success */ return 0; } diff --git a/src/protocols/rdp/client.h b/src/protocols/rdp/client.h index 376a1619..43161cae 100644 --- a/src/protocols/rdp/client.h +++ b/src/protocols/rdp/client.h @@ -20,37 +20,13 @@ * THE SOFTWARE. */ - -#ifndef _GUAC_RDP_CLIENT_H -#define _GUAC_RDP_CLIENT_H +#ifndef GUAC_RDP_CLIENT_H +#define GUAC_RDP_CLIENT_H #include "config.h" -#include "guac_clipboard.h" -#include "guac_list.h" -#include "guac_surface.h" -#include "rdp_fs.h" -#include "rdp_keymap.h" -#include "rdp_settings.h" - -#ifdef ENABLE_COMMON_SSH -#include "guac_sftp.h" -#include "guac_ssh.h" -#include "guac_ssh_user.h" -#endif - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT -#include "rdp_disp.h" -#endif - -#include -#include -#include #include -#include -#include - /** * The maximum duration of a frame in milliseconds. */ @@ -107,152 +83,12 @@ */ #define GUAC_RDP_AUDIO_BPS 16 - /** - * Client data that will remain accessible through the guac_client. - * This should generally include data commonly used by Guacamole handlers. + * Handler which frees all data associated with the guac_client. + * + * @param client The guac_client whose data should be freed. + * @return Zero if the client was successfully freed, non-zero otherwise. */ -typedef struct rdp_guac_client_data { - - /** - * Pointer to the FreeRDP client instance handling the current connection. - */ - freerdp* rdp_inst; - - /** - * All settings associated with the current or pending RDP connection. - */ - guac_rdp_settings settings; - - /** - * Button mask containing the OR'd value of all currently pressed buttons. - */ - int mouse_button_mask; - - /** - * Foreground color for any future glyphs. - */ - uint32_t glyph_color; - - /** - * The display. - */ - guac_common_surface* default_surface; - - /** - * The surface that GDI operations should draw to. RDP messages exist which - * change this surface to allow drawing to occur off-screen. - */ - guac_common_surface* current_surface; - - /** - * The keymap to use when translating keysyms into scancodes or sequences - * of scancodes for RDP. - */ - guac_rdp_static_keymap keymap; - - /** - * The state of all keys, based on whether events for pressing/releasing - * particular keysyms have been received. This is necessary in order to - * determine which keys must be released/pressed when a particular - * keysym can only be typed through a sequence of scancodes (such as - * an Alt-code) because the server-side keymap does not support that - * keysym. - */ - guac_rdp_keysym_state_map keysym_state; - - /** - * The current clipboard contents. - */ - guac_common_clipboard* clipboard; - - /** - * The format of the clipboard which was requested. Data received from - * the RDP server should conform to this format. This will be one of - * several legal clipboard format values defined within FreeRDP, such as - * CB_FORMAT_TEXT. - */ - int requested_clipboard_format; - - /** - * Audio output, if any. - */ - guac_audio_stream* audio; - - /** - * The filesystem being shared, if any. - */ - guac_rdp_fs* filesystem; - -#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 - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /** - * Display size update module. - */ - guac_rdp_disp* disp; -#endif - - /** - * List of all available static virtual channels. - */ - guac_common_list* available_svc; - - /** - * Lock which is locked and unlocked for each RDP message. - */ - pthread_mutex_t rdp_lock; - - /** - * Common attributes for locks. - */ - pthread_mutexattr_t attributes; - -} rdp_guac_client_data; - -/** - * Client data that will remain accessible through the RDP context. - * This should generally include data commonly used by FreeRDP handlers. - */ -typedef struct rdp_freerdp_context { - - /** - * The parent context. THIS MUST BE THE FIRST ELEMENT. - */ - rdpContext _p; - - /** - * Pointer to the guac_client instance handling the RDP connection with - * this context. - */ - guac_client* client; - - /** - * Color conversion structure to be used to convert RDP images to PNGs. - */ - CLRCONV* clrconv; - - /** - * The current color palette, as received from the RDP server. - */ - UINT32 palette[256]; - -} rdp_freerdp_context; +int guac_rdp_client_free_handler(guac_client* client); #endif - diff --git a/src/protocols/rdp/guac_handlers.c b/src/protocols/rdp/guac_handlers.c deleted file mode 100644 index 86eec9c4..00000000 --- a/src/protocols/rdp/guac_handlers.c +++ /dev/null @@ -1,525 +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_handlers.h" -#include "guac_list.h" -#include "guac_surface.h" -#include "rdp_cliprdr.h" -#include "rdp_keymap.h" -#include "rdp_fs.h" -#include "rdp_rail.h" -#include "rdp_stream.h" - -#ifdef ENABLE_COMMON_SSH -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT -#include "rdp_disp.h" -#endif - -#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H -#include -#else -#include "compat/client-cliprdr.h" -#endif - -#ifdef LEGACY_FREERDP -#include "compat/rail.h" -#else -#include -#endif - -#include -#include -#include -#include -#include - -void __guac_rdp_update_keysyms(guac_client* client, const int* keysym_string, int from, int to); -int __guac_rdp_send_keysym(guac_client* client, int keysym, int pressed); - -int rdp_guac_client_free_handler(guac_client* client) { - - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; - - freerdp* rdp_inst = guac_client_data->rdp_inst; - rdpChannels* channels = rdp_inst->context->channels; - - /* Clean up RDP client */ - freerdp_channels_close(channels, rdp_inst); - freerdp_channels_free(channels); - freerdp_disconnect(rdp_inst); - freerdp_clrconv_free(((rdp_freerdp_context*) rdp_inst->context)->clrconv); - cache_free(rdp_inst->context->cache); - freerdp_free(rdp_inst); - - /* Clean up filesystem, if allocated */ - if (guac_client_data->filesystem != NULL) - guac_rdp_fs_free(guac_client_data->filesystem); - -#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 - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /* Free display update module */ - guac_rdp_disp_free(guac_client_data->disp); -#endif - - /* Free SVC list */ - guac_common_list_free(guac_client_data->available_svc); - - /* Free client data */ - guac_common_clipboard_free(guac_client_data->clipboard); - guac_common_surface_free(guac_client_data->default_surface); - free(guac_client_data); - - return 0; - -} - -static int rdp_guac_client_wait_for_messages(guac_client* client, int timeout_usecs) { - - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - freerdp* rdp_inst = guac_client_data->rdp_inst; - rdpChannels* channels = rdp_inst->context->channels; - - int result; - int index; - int max_fd, fd; - void* read_fds[32]; - void* write_fds[32]; - int read_count = 0; - int write_count = 0; - fd_set rfds, wfds; - - struct timeval timeout = { - .tv_sec = 0, - .tv_usec = timeout_usecs - }; - - /* Get RDP fds */ - if (!freerdp_get_fds(rdp_inst, read_fds, &read_count, write_fds, &write_count)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP file descriptors."); - return -1; - } - - /* Get channel fds */ - if (!freerdp_channels_get_fds(channels, rdp_inst, read_fds, &read_count, write_fds, - &write_count)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP channel file descriptors."); - return -1; - } - - /* Construct read fd_set */ - max_fd = 0; - FD_ZERO(&rfds); - for (index = 0; index < read_count; index++) { - fd = (int)(long) (read_fds[index]); - if (fd > max_fd) - max_fd = fd; - FD_SET(fd, &rfds); - } - - /* Construct write fd_set */ - FD_ZERO(&wfds); - for (index = 0; index < write_count; index++) { - fd = (int)(long) (write_fds[index]); - if (fd > max_fd) - max_fd = fd; - FD_SET(fd, &wfds); - } - - /* If no file descriptors, error */ - if (max_fd == 0) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "No file descriptors associated with RDP connection."); - return -1; - } - - /* Wait for all RDP file descriptors */ - result = select(max_fd + 1, &rfds, &wfds, NULL, &timeout); - if (result < 0) { - - /* If error ignorable, pretend timout occurred */ - if (errno == EAGAIN - || errno == EWOULDBLOCK - || errno == EINPROGRESS - || errno == EINTR) - return 0; - - /* Otherwise, return as error */ - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error waiting for file descriptor."); - return -1; - - } - - /* Return wait result */ - return result; - -} - -int rdp_guac_client_handle_messages(guac_client* client) { - - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - freerdp* rdp_inst = guac_client_data->rdp_inst; - rdpChannels* channels = rdp_inst->context->channels; - wMessage* event; - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /* Update remote display size */ - pthread_mutex_lock(&(guac_client_data->rdp_lock)); - guac_rdp_disp_update_size(guac_client_data->disp, rdp_inst->context); - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); -#endif - - /* Wait for messages */ - int wait_result = rdp_guac_client_wait_for_messages(client, 250000); - guac_timestamp frame_start = guac_timestamp_current(); - while (wait_result > 0) { - - guac_timestamp frame_end; - int frame_remaining; - - pthread_mutex_lock(&(guac_client_data->rdp_lock)); - - /* Check the libfreerdp fds */ - if (!freerdp_check_fds(rdp_inst)) { - guac_client_log(client, GUAC_LOG_DEBUG, "Error handling RDP file descriptors"); - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); - return 1; - } - - /* Check channel fds */ - if (!freerdp_channels_check_fds(channels, rdp_inst)) { - guac_client_log(client, GUAC_LOG_DEBUG, "Error handling RDP channel file descriptors"); - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); - return 1; - } - - /* Check for channel events */ - event = freerdp_channels_pop_event(channels); - if (event) { - - /* Handle channel events (clipboard and RAIL) */ -#ifdef LEGACY_EVENT - if (event->event_class == CliprdrChannel_Class) - guac_rdp_process_cliprdr_event(client, event); - else if (event->event_class == RailChannel_Class) - guac_rdp_process_rail_event(client, event); -#else - if (GetMessageClass(event->id) == CliprdrChannel_Class) - guac_rdp_process_cliprdr_event(client, event); - else if (GetMessageClass(event->id) == RailChannel_Class) - guac_rdp_process_rail_event(client, event); -#endif - - freerdp_event_free(event); - - } - - /* Handle RDP disconnect */ - if (freerdp_shall_disconnect(rdp_inst)) { - guac_client_log(client, GUAC_LOG_INFO, "RDP server closed connection"); - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); - return 1; - } - - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); - - /* Calculate time remaining in frame */ - frame_end = guac_timestamp_current(); - frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION - frame_end; - - /* Wait again if frame remaining */ - if (frame_remaining > 0) - wait_result = rdp_guac_client_wait_for_messages(client, - GUAC_RDP_FRAME_TIMEOUT*1000); - else - break; - - } - - /* If an error occurred, fail */ - if (wait_result < 0) - return 1; - - /* Success */ - guac_common_surface_flush(guac_client_data->default_surface); - return 0; - -} - -int rdp_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) { - - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - freerdp* rdp_inst = guac_client_data->rdp_inst; - - pthread_mutex_lock(&(guac_client_data->rdp_lock)); - - /* If button mask unchanged, just send move event */ - if (mask == guac_client_data->mouse_button_mask) - rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_MOVE, x, y); - - /* Otherwise, send events describing button change */ - else { - - /* Mouse buttons which have JUST become released */ - int released_mask = guac_client_data->mouse_button_mask & ~mask; - - /* Mouse buttons which have JUST become pressed */ - int pressed_mask = ~guac_client_data->mouse_button_mask & mask; - - /* Release event */ - if (released_mask & 0x07) { - - /* Calculate flags */ - int flags = 0; - if (released_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; - if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; - if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; - - rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y); - - } - - /* Press event */ - if (pressed_mask & 0x07) { - - /* Calculate flags */ - int flags = PTR_FLAGS_DOWN; - if (pressed_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; - if (pressed_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; - if (pressed_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; - if (pressed_mask & 0x08) flags |= PTR_FLAGS_WHEEL | 0x78; - if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88; - - /* Send event */ - rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y); - - } - - /* Scroll event */ - if (pressed_mask & 0x18) { - - /* Down */ - if (pressed_mask & 0x08) - rdp_inst->input->MouseEvent( - rdp_inst->input, - PTR_FLAGS_WHEEL | 0x78, - x, y); - - /* Up */ - if (pressed_mask & 0x10) - rdp_inst->input->MouseEvent( - rdp_inst->input, - PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88, - x, y); - - } - - guac_client_data->mouse_button_mask = mask; - } - - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); - - return 0; -} - -int __guac_rdp_send_keysym(guac_client* client, int keysym, int pressed) { - - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - freerdp* rdp_inst = guac_client_data->rdp_inst; - - /* If keysym can be in lookup table */ - if (GUAC_RDP_KEYSYM_STORABLE(keysym)) { - - int pressed_flags; - - /* Look up scancode mapping */ - const guac_rdp_keysym_desc* keysym_desc = - &GUAC_RDP_KEYSYM_LOOKUP(guac_client_data->keymap, keysym); - - /* If defined, send event */ - if (keysym_desc->scancode != 0) { - - pthread_mutex_lock(&(guac_client_data->rdp_lock)); - - /* If defined, send any prerequesite keys that must be set */ - if (keysym_desc->set_keysyms != NULL) - __guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 1); - - /* If defined, release any keys that must be cleared */ - if (keysym_desc->clear_keysyms != NULL) - __guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 0); - - /* Determine proper event flag for pressed state */ - if (pressed) - pressed_flags = KBD_FLAGS_DOWN; - else - pressed_flags = KBD_FLAGS_RELEASE; - - /* Send actual key */ - rdp_inst->input->KeyboardEvent(rdp_inst->input, keysym_desc->flags | pressed_flags, - keysym_desc->scancode); - - /* If defined, release any keys that were originally released */ - if (keysym_desc->set_keysyms != NULL) - __guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 0); - - /* If defined, send any keys that were originally set */ - if (keysym_desc->clear_keysyms != NULL) - __guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 1); - - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); - - return 0; - - } - } - - /* Fall back to unicode events if undefined inside current keymap */ - - /* Only send when key pressed - Unicode events do not have - * DOWN/RELEASE flags */ - if (pressed) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Sending keysym 0x%x as Unicode", keysym); - - /* Translate keysym into codepoint */ - int codepoint; - if (keysym <= 0xFF) - codepoint = keysym; - else if (keysym >= 0x1000000) - codepoint = keysym & 0xFFFFFF; - else { - guac_client_log(client, GUAC_LOG_DEBUG, - "Unmapped keysym has no equivalent unicode " - "value: 0x%x", keysym); - return 0; - } - - pthread_mutex_lock(&(guac_client_data->rdp_lock)); - - /* Send Unicode event */ - rdp_inst->input->UnicodeKeyboardEvent( - rdp_inst->input, - 0, codepoint); - - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); - - } - - return 0; -} - -void __guac_rdp_update_keysyms(guac_client* client, const int* keysym_string, int from, int to) { - - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - int keysym; - - /* Send all keysyms in string, NULL terminated */ - while ((keysym = *keysym_string) != 0) { - - /* Get current keysym state */ - int current_state = GUAC_RDP_KEYSYM_LOOKUP(guac_client_data->keysym_state, keysym); - - /* If key is currently in given state, send event for changing it to specified "to" state */ - if (current_state == from) - __guac_rdp_send_keysym(client, *keysym_string, to); - - /* Next keysym */ - keysym_string++; - - } - -} - -int rdp_guac_client_key_handler(guac_client* client, int keysym, int pressed) { - - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - - /* Update keysym state */ - if (GUAC_RDP_KEYSYM_STORABLE(keysym)) - GUAC_RDP_KEYSYM_LOOKUP(guac_client_data->keysym_state, keysym) = pressed; - - return __guac_rdp_send_keysym(client, keysym, pressed); - -} - -int rdp_guac_client_size_handler(guac_client* client, int width, int height) { - -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; - - freerdp* rdp_inst = guac_client_data->rdp_inst; - - /* Convert client pixels to remote pixels */ - width = width * guac_client_data->settings.resolution - / client->info.optimal_resolution; - - height = height * guac_client_data->settings.resolution - / client->info.optimal_resolution; - - /* Send display update */ - pthread_mutex_lock(&(guac_client_data->rdp_lock)); - guac_rdp_disp_set_size(guac_client_data->disp, rdp_inst->context, - width, height); - pthread_mutex_unlock(&(guac_client_data->rdp_lock)); -#endif - - return 0; - -} - diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c b/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c index 5ba08c03..898d8bd8 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_fs_service.c @@ -23,7 +23,7 @@ #include "config.h" -#include "client.h" +#include "rdp.h" #include "rdpdr_fs_messages.h" #include "rdpdr_messages.h" #include "rdpdr_service.h" @@ -135,7 +135,8 @@ static void guac_rdpdr_device_fs_free_handler(guac_rdpdr_device* device) { void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr) { - rdp_guac_client_data* data = (rdp_guac_client_data*) rdpdr->client->data; + guac_client* client = rdpdr->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; int id = rdpdr->devices_registered++; /* Get new device */ @@ -152,12 +153,10 @@ void guac_rdpdr_register_fs(guac_rdpdrPlugin* rdpdr) { device->free_handler = guac_rdpdr_device_fs_free_handler; /* Init data */ - device->data = data->filesystem; + device->data = rdp_client->filesystem; - /* Announce filesystem to client */ - guac_protocol_send_filesystem(rdpdr->client->socket, - data->filesystem->object, "Shared Drive"); - guac_socket_flush(rdpdr->client->socket); + /* Announce filesystem to owner */ + guac_client_for_owner(client, guac_rdp_fs_expose, rdp_client->filesystem); } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_service.c b/src/protocols/rdp/guac_rdpdr/rdpdr_service.c index 9be53f7c..bc85e4d6 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_service.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_service.c @@ -22,7 +22,7 @@ #include "config.h" -#include "client.h" +#include "rdp.h" #include "rdp_fs.h" #include "rdp_settings.h" #include "rdp_stream.h" @@ -91,18 +91,18 @@ void guac_rdpdr_process_connect(rdpSvcPlugin* plugin) { plugin->channel_entry_points.pExtendedData = NULL; /* Get data from client */ - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Init plugin */ rdpdr->client = client; rdpdr->devices_registered = 0; /* Register printer if enabled */ - if (client_data->settings.printing_enabled) + if (rdp_client->settings->printing_enabled) guac_rdpdr_register_printer(rdpdr); /* Register drive if enabled */ - if (client_data->settings.drive_enabled) + if (rdp_client->settings->drive_enabled) guac_rdpdr_register_fs(rdpdr); /* Log that printing, etc. has been loaded */ diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c b/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c index a0827d3f..f54bcaf2 100644 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c +++ b/src/protocols/rdp/guac_rdpsnd/rdpsnd_messages.c @@ -22,7 +22,7 @@ #include "config.h" -#include "client.h" +#include "rdp.h" #include "rdpsnd_messages.h" #include "rdpsnd_service.h" @@ -56,10 +56,10 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, /* Get associated client data */ guac_client* client = rdpsnd->client; - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Get audio stream from client data */ - guac_audio_stream* audio = client_data->audio; + guac_audio_stream* audio = rdp_client->audio; /* Format header */ Stream_Seek(input_stream, 14); @@ -188,7 +188,7 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, Stream_SetPointer(output_stream, output_stream_end); /* Send accepted formats */ - pthread_mutex_lock(&(client_data->rdp_lock)); + pthread_mutex_lock(&(rdp_client->rdp_lock)); svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream); /* If version greater than 6, must send Quality Mode PDU */ @@ -205,7 +205,7 @@ void guac_rdpsnd_formats_handler(guac_rdpsndPlugin* rdpsnd, svc_plugin_send((rdpSvcPlugin*)rdpsnd, output_stream); } - pthread_mutex_unlock(&(client_data->rdp_lock)); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); } @@ -218,7 +218,7 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd, /* Get associated client data */ guac_client* client = rdpsnd->client; - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Read timestamp and data size */ Stream_Read_UINT16(input_stream, rdpsnd->server_timestamp); @@ -232,9 +232,9 @@ void guac_rdpsnd_training_handler(guac_rdpsndPlugin* rdpsnd, Stream_Write_UINT16(output_stream, rdpsnd->server_timestamp); Stream_Write_UINT16(output_stream, data_size); - pthread_mutex_lock(&(client_data->rdp_lock)); + pthread_mutex_lock(&(rdp_client->rdp_lock)); svc_plugin_send((rdpSvcPlugin*) rdpsnd, output_stream); - pthread_mutex_unlock(&(client_data->rdp_lock)); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); } @@ -245,10 +245,10 @@ void guac_rdpsnd_wave_info_handler(guac_rdpsndPlugin* rdpsnd, /* Get associated client data */ guac_client* client = rdpsnd->client; - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Get audio stream from client data */ - guac_audio_stream* audio = client_data->audio; + guac_audio_stream* audio = rdp_client->audio; /* Read wave information */ Stream_Read_UINT16(input_stream, rdpsnd->server_timestamp); @@ -283,10 +283,10 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, /* Get associated client data */ guac_client* client = rdpsnd->client; - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Get audio stream from client data */ - guac_audio_stream* audio = client_data->audio; + guac_audio_stream* audio = rdp_client->audio; /* Wave Confirmation PDU */ wStream* output_stream = Stream_New(NULL, 8); @@ -313,9 +313,9 @@ void guac_rdpsnd_wave_handler(guac_rdpsndPlugin* rdpsnd, Stream_Write_UINT8(output_stream, 0); /* Send Wave Confirmation PDU */ - pthread_mutex_lock(&(client_data->rdp_lock)); + pthread_mutex_lock(&(rdp_client->rdp_lock)); svc_plugin_send(plugin, output_stream); - pthread_mutex_unlock(&(client_data->rdp_lock)); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); /* We no longer expect to receive wave data */ rdpsnd->next_pdu_is_wave = FALSE; diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c new file mode 100644 index 00000000..e87440a3 --- /dev/null +++ b/src/protocols/rdp/input.c @@ -0,0 +1,280 @@ +/* + * 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 "input.h" +#include "rdp.h" +#include "rdp_keymap.h" + +#include +#include +#include + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT +#include "rdp_disp.h" +#endif + +#include +#include + +int guac_rdp_send_keysym(guac_client* client, int keysym, int pressed) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + freerdp* rdp_inst = rdp_client->rdp_inst; + + /* Skip if not yet connected */ + if (rdp_inst == NULL) + return 0; + + /* If keysym can be in lookup table */ + if (GUAC_RDP_KEYSYM_STORABLE(keysym)) { + + int pressed_flags; + + /* Look up scancode mapping */ + const guac_rdp_keysym_desc* keysym_desc = + &GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keymap, keysym); + + /* If defined, send event */ + if (keysym_desc->scancode != 0) { + + pthread_mutex_lock(&(rdp_client->rdp_lock)); + + /* If defined, send any prerequesite keys that must be set */ + if (keysym_desc->set_keysyms != NULL) + guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 1); + + /* If defined, release any keys that must be cleared */ + if (keysym_desc->clear_keysyms != NULL) + guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 0); + + /* Determine proper event flag for pressed state */ + if (pressed) + pressed_flags = KBD_FLAGS_DOWN; + else + pressed_flags = KBD_FLAGS_RELEASE; + + /* Send actual key */ + rdp_inst->input->KeyboardEvent(rdp_inst->input, keysym_desc->flags | pressed_flags, + keysym_desc->scancode); + + /* If defined, release any keys that were originally released */ + if (keysym_desc->set_keysyms != NULL) + guac_rdp_update_keysyms(client, keysym_desc->set_keysyms, 0, 0); + + /* If defined, send any keys that were originally set */ + if (keysym_desc->clear_keysyms != NULL) + guac_rdp_update_keysyms(client, keysym_desc->clear_keysyms, 1, 1); + + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + + return 0; + + } + } + + /* Fall back to unicode events if undefined inside current keymap */ + + /* Only send when key pressed - Unicode events do not have + * DOWN/RELEASE flags */ + if (pressed) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Sending keysym 0x%x as Unicode", keysym); + + /* Translate keysym into codepoint */ + int codepoint; + if (keysym <= 0xFF) + codepoint = keysym; + else if (keysym >= 0x1000000) + codepoint = keysym & 0xFFFFFF; + else { + guac_client_log(client, GUAC_LOG_DEBUG, + "Unmapped keysym has no equivalent unicode " + "value: 0x%x", keysym); + return 0; + } + + pthread_mutex_lock(&(rdp_client->rdp_lock)); + + /* Send Unicode event */ + rdp_inst->input->UnicodeKeyboardEvent( + rdp_inst->input, + 0, codepoint); + + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + + } + + return 0; +} + +void guac_rdp_update_keysyms(guac_client* client, const int* keysym_string, int from, int to) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + int keysym; + + /* Send all keysyms in string, NULL terminated */ + while ((keysym = *keysym_string) != 0) { + + /* Get current keysym state */ + int current_state = GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keysym_state, keysym); + + /* If key is currently in given state, send event for changing it to specified "to" state */ + if (current_state == from) + guac_rdp_send_keysym(client, *keysym_string, to); + + /* Next keysym */ + keysym_string++; + + } + +} + +int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + freerdp* rdp_inst = rdp_client->rdp_inst; + + /* Store current mouse location */ + guac_common_cursor_move(rdp_client->display->cursor, user, x, y); + + /* Skip if not yet connected */ + if (rdp_inst == NULL) + return 0; + + pthread_mutex_lock(&(rdp_client->rdp_lock)); + + /* If button mask unchanged, just send move event */ + if (mask == rdp_client->mouse_button_mask) + rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_MOVE, x, y); + + /* Otherwise, send events describing button change */ + else { + + /* Mouse buttons which have JUST become released */ + int released_mask = rdp_client->mouse_button_mask & ~mask; + + /* Mouse buttons which have JUST become pressed */ + int pressed_mask = ~rdp_client->mouse_button_mask & mask; + + /* Release event */ + if (released_mask & 0x07) { + + /* Calculate flags */ + int flags = 0; + if (released_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; + if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; + if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; + + rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y); + + } + + /* Press event */ + if (pressed_mask & 0x07) { + + /* Calculate flags */ + int flags = PTR_FLAGS_DOWN; + if (pressed_mask & 0x01) flags |= PTR_FLAGS_BUTTON1; + if (pressed_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; + if (pressed_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; + if (pressed_mask & 0x08) flags |= PTR_FLAGS_WHEEL | 0x78; + if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88; + + /* Send event */ + rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y); + + } + + /* Scroll event */ + if (pressed_mask & 0x18) { + + /* Down */ + if (pressed_mask & 0x08) + rdp_inst->input->MouseEvent( + rdp_inst->input, + PTR_FLAGS_WHEEL | 0x78, + x, y); + + /* Up */ + if (pressed_mask & 0x10) + rdp_inst->input->MouseEvent( + rdp_inst->input, + PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88, + x, y); + + } + + rdp_client->mouse_button_mask = mask; + } + + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + + return 0; +} + +int guac_rdp_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Update keysym state */ + if (GUAC_RDP_KEYSYM_STORABLE(keysym)) + GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keysym_state, keysym) = pressed; + + return guac_rdp_send_keysym(client, keysym, pressed); + +} + +int guac_rdp_user_size_handler(guac_user* user, int width, int height) { + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + freerdp* rdp_inst = rdp_client->rdp_inst; + + /* Skip if not yet connected */ + if (rdp_inst == NULL) + return 0; + + /* Convert client pixels to remote pixels */ + width = width * rdp_client->settings->resolution + / user->info.optimal_resolution; + + height = height * rdp_client->settings->resolution + / user->info.optimal_resolution; + + /* Send display update */ + pthread_mutex_lock(&(rdp_client->rdp_lock)); + guac_rdp_disp_set_size(rdp_client->disp, rdp_inst->context, width, height); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); +#endif + + return 0; + +} + diff --git a/src/protocols/rdp/input.h b/src/protocols/rdp/input.h new file mode 100644 index 00000000..68b4eff4 --- /dev/null +++ b/src/protocols/rdp/input.h @@ -0,0 +1,102 @@ +/* + * 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_RDP_INPUT_H +#define GUAC_RDP_INPUT_H + +#include +#include + +/** + * Presses or releases the given keysym, sending an appropriate set of key + * events to the RDP server. The key events sent will depend on the current + * keymap. + * + * @param client The guac_client associated with the current RDP session. + * @param keysym The keysym being pressed or released. + * @param pressed Zero if the keysym is being released, non-zero otherwise. + * @return Zero if the keys were successfully sent, non-zero otherwise. + */ +int guac_rdp_send_keysym(guac_client* client, int keysym, int pressed); + +/** + * For every keysym in the given NULL-terminated array of keysyms, update + * the current state of that key conditionally. For each key in the "from" + * state (0 being released and 1 being pressed), that key will be updated + * to the "to" state. + * + * @param client The guac_client associated with the current RDP session. + * + * @param keysym_string + * A NULL-terminated array of keysyms, each of which will be updated. + * + * @param from + * 0 if the state of currently-released keys should be updated, or 1 if + * the state of currently-pressed keys should be updated. + * + * @param to + * 0 if the keys being updated should be marked as released, or 1 if + * the keys being updated should be marked as pressed. + */ +void guac_rdp_update_keysyms(guac_client* client, const int* keysym_string, + int from, int to); + +/** + * Handler for Guacamole user mouse events. + * + * @param user The guac_user originating the mouse event. + * @param x The current X location of the mouse pointer. + * @param y The current Y location of the mouse pointer. + * + * @param mask + * Button mask denoting the pressed/released state of each mouse button. + * The lowest-order bit represents button 0 (the left mouse button), and + * so on through the higher-order bits. Most mice have no more than 5 + * mouse buttons. + * + * @return + * Zero if the mouse event was successfully handled, non-zero otherwise. + */ +int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask); + +/** + * Handler for Guacamole user key events. + * + * @param user The guac_user originating the key event. + * @param keysym The keysym being pressed or released. + * @param pressed Zero if the keysym is being released, non-zero otherwise. + * @return Zero if the key event was successfully handled, non-zero otherwise. + */ +int guac_rdp_user_key_handler(guac_user* user, int keysym, int pressed); + +/** + * Handler for Guacamole user size events. + * + * @param user The guac_user originating the size event. + * @param width The new display width, in pixels. + * @param height The new display height, in pixels. + * @return Zero if the size event was successfully handled, non-zero otherwise. + */ +int guac_rdp_user_size_handler(guac_user* user, int width, int height); + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c new file mode 100644 index 00000000..01096627 --- /dev/null +++ b/src/protocols/rdp/rdp.c @@ -0,0 +1,791 @@ +/* + * 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 "rdp.h" +#include "rdp_bitmap.h" +#include "rdp_cliprdr.h" +#include "rdp_gdi.h" +#include "rdp_glyph.h" +#include "rdp_keymap.h" +#include "rdp_pointer.h" +#include "rdp_rail.h" +#include "rdp_stream.h" +#include "rdp_svc.h" + +#ifdef ENABLE_COMMON_SSH +#include +#include +#include +#endif + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT +#include "rdp_disp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H +#include +#else +#include "compat/client-cliprdr.h" +#endif + +#ifdef HAVE_FREERDP_CLIENT_DISP_H +#include +#endif + +#ifdef HAVE_FREERDP_EVENT_PUBSUB +#include +#endif + +#ifdef LEGACY_FREERDP +#include "compat/rail.h" +#else +#include +#endif + +#ifdef ENABLE_WINPR +#include +#else +#include "compat/winpr-wtypes.h" +#endif + +#ifdef HAVE_FREERDP_ADDIN_H +#include +#endif + +#ifdef HAVE_FREERDP_CLIENT_CHANNELS_H +#include +#endif + +#ifdef HAVE_FREERDP_VERSION_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if defined(FREERDP_VERSION_MAJOR) && (FREERDP_VERSION_MAJOR > 1 || FREERDP_VERSION_MINOR >= 2) +int __guac_receive_channel_data(freerdp* rdp_inst, UINT16 channelId, BYTE* data, int size, int flags, int total_size) { +#else +int __guac_receive_channel_data(freerdp* rdp_inst, int channelId, UINT8* data, int size, int flags, int total_size) { +#endif + return freerdp_channels_data(rdp_inst, channelId, data, size, flags, total_size); +} + +#ifdef HAVE_FREERDP_EVENT_PUBSUB +/** + * Called whenever a channel connects via the PubSub event system within + * FreeRDP. + * + * @param context The rdpContext associated with the active RDP session. + * @param e Event-specific arguments, mainly the name of the channel, and a + * reference to the associated plugin loaded for that channel by + * FreeRDP. + */ +static void guac_rdp_channel_connected(rdpContext* context, + ChannelConnectedEventArgs* e) { + +#ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL + /* Store reference to the display update plugin once it's connected */ + if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) { + + DispClientContext* disp = (DispClientContext*) e->pInterface; + + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Init module with current display size */ + guac_rdp_disp_set_size(rdp_client->disp, context, + guac_rdp_get_width(context->instance), + guac_rdp_get_height(context->instance)); + + /* Store connected channel */ + guac_rdp_disp_connect(rdp_client->disp, disp); + guac_client_log(client, GUAC_LOG_DEBUG, + "Display update channel connected."); + + } +#endif + +} +#endif + +BOOL rdp_freerdp_pre_connect(freerdp* instance) { + + rdpContext* context = instance->context; + guac_client* client = ((rdp_freerdp_context*) context)->client; + rdpChannels* channels = context->channels; + rdpBitmap* bitmap; + rdpGlyph* glyph; + rdpPointer* pointer; + rdpPrimaryUpdate* primary; + CLRCONV* clrconv; + + guac_rdp_client* rdp_client = + (guac_rdp_client*) client->data; + +#ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER + /* Init FreeRDP add-in provider */ + freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); +#endif + +#ifdef HAVE_FREERDP_EVENT_PUBSUB + /* Subscribe to and handle channel connected events */ + PubSub_SubscribeChannelConnected(context->pubSub, + (pChannelConnectedEventHandler) guac_rdp_channel_connected); +#endif + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + /* Load virtual channel management plugin */ + if (freerdp_channels_load_plugin(channels, instance->settings, + "drdynvc", instance->settings)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load drdynvc plugin."); + + /* Init display update plugin */ + rdp_client->disp = guac_rdp_disp_alloc(); + guac_rdp_disp_load_plugin(instance->context); +#endif + + /* Load clipboard plugin */ + if (freerdp_channels_load_plugin(channels, instance->settings, + "cliprdr", NULL)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load cliprdr plugin. Clipboard will not work."); + + /* If audio enabled, choose an encoder */ + if (rdp_client->settings->audio_enabled) { + + rdp_client->audio = guac_audio_stream_alloc(client, NULL, + GUAC_RDP_AUDIO_RATE, + GUAC_RDP_AUDIO_CHANNELS, + GUAC_RDP_AUDIO_BPS); + + /* Warn if no audio encoding is available */ + if (rdp_client->audio == NULL) + guac_client_log(client, GUAC_LOG_INFO, + "No available audio encoding. Sound disabled."); + + } /* end if audio enabled */ + + /* Load filesystem if drive enabled */ + if (rdp_client->settings->drive_enabled) + rdp_client->filesystem = + guac_rdp_fs_alloc(client, rdp_client->settings->drive_path, + rdp_client->settings->create_drive_path); + + /* If RDPSND/RDPDR required, load them */ + if (rdp_client->settings->printing_enabled + || rdp_client->settings->drive_enabled + || rdp_client->settings->audio_enabled) { + + /* Load RDPDR plugin */ + if (freerdp_channels_load_plugin(channels, instance->settings, + "guacdr", client)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load guacdr plugin. Drive redirection and " + "printing will not work. Sound MAY not work."); + + /* Load RDPSND plugin */ + if (freerdp_channels_load_plugin(channels, instance->settings, + "guacsnd", client)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load guacsnd alongside guacdr plugin. Sound " + "will not work. Drive redirection and printing MAY not " + "work."); + + } + + /* Load RAIL plugin if RemoteApp in use */ + if (rdp_client->settings->remote_app != NULL) { + +#ifdef LEGACY_FREERDP + RDP_PLUGIN_DATA* plugin_data = malloc(sizeof(RDP_PLUGIN_DATA) * 2); + + plugin_data[0].size = sizeof(RDP_PLUGIN_DATA); + plugin_data[0].data[0] = rdp_client->settings->remote_app; + plugin_data[0].data[1] = rdp_client->settings->remote_app_dir; + plugin_data[0].data[2] = rdp_client->settings->remote_app_args; + plugin_data[0].data[3] = NULL; + + plugin_data[1].size = 0; + + /* Attempt to load rail */ + if (freerdp_channels_load_plugin(channels, instance->settings, + "rail", plugin_data)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load rail plugin. RemoteApp will not work."); +#else + /* Attempt to load rail */ + if (freerdp_channels_load_plugin(channels, instance->settings, + "rail", instance->settings)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load rail plugin. RemoteApp will not work."); +#endif + + } + + /* Load SVC plugin instances for all static channels */ + if (rdp_client->settings->svc_names != NULL) { + + char** current = rdp_client->settings->svc_names; + do { + + guac_rdp_svc* svc = guac_rdp_alloc_svc(client, *current); + + /* Attempt to load guacsvc plugin for new static channel */ + if (freerdp_channels_load_plugin(channels, instance->settings, + "guacsvc", svc)) { + guac_client_log(client, GUAC_LOG_WARNING, + "Cannot create static channel \"%s\": failed to load guacsvc plugin.", + svc->name); + guac_rdp_free_svc(svc); + } + + /* Store and log on success */ + else { + guac_rdp_add_svc(client, svc); + guac_client_log(client, GUAC_LOG_INFO, "Created static channel \"%s\"...", + svc->name); + } + + } while (*(++current) != NULL); + + } + + /* Init color conversion structure */ + clrconv = calloc(1, sizeof(CLRCONV)); + clrconv->alpha = 1; + clrconv->invert = 0; + clrconv->rgb555 = 0; + clrconv->palette = calloc(1, sizeof(rdpPalette)); + ((rdp_freerdp_context*) context)->clrconv = clrconv; + + /* Init FreeRDP cache */ + instance->context->cache = cache_new(instance->settings); + + /* Set up bitmap handling */ + bitmap = calloc(1, sizeof(rdpBitmap)); + bitmap->size = sizeof(guac_rdp_bitmap); + bitmap->New = guac_rdp_bitmap_new; + bitmap->Free = guac_rdp_bitmap_free; + bitmap->Paint = guac_rdp_bitmap_paint; + bitmap->Decompress = guac_rdp_bitmap_decompress; + bitmap->SetSurface = guac_rdp_bitmap_setsurface; + graphics_register_bitmap(context->graphics, bitmap); + free(bitmap); + + /* Set up glyph handling */ + glyph = calloc(1, sizeof(rdpGlyph)); + glyph->size = sizeof(guac_rdp_glyph); + glyph->New = guac_rdp_glyph_new; + glyph->Free = guac_rdp_glyph_free; + glyph->Draw = guac_rdp_glyph_draw; + glyph->BeginDraw = guac_rdp_glyph_begindraw; + glyph->EndDraw = guac_rdp_glyph_enddraw; + graphics_register_glyph(context->graphics, glyph); + free(glyph); + + /* Set up pointer handling */ + pointer = calloc(1, sizeof(rdpPointer)); + pointer->size = sizeof(guac_rdp_pointer); + pointer->New = guac_rdp_pointer_new; + pointer->Free = guac_rdp_pointer_free; + pointer->Set = guac_rdp_pointer_set; +#ifdef HAVE_RDPPOINTER_SETNULL + pointer->SetNull = guac_rdp_pointer_set_null; +#endif +#ifdef HAVE_RDPPOINTER_SETDEFAULT + pointer->SetDefault = guac_rdp_pointer_set_default; +#endif + graphics_register_pointer(context->graphics, pointer); + free(pointer); + + /* Set up GDI */ + instance->update->DesktopResize = guac_rdp_gdi_desktop_resize; + instance->update->EndPaint = guac_rdp_gdi_end_paint; + instance->update->Palette = guac_rdp_gdi_palette_update; + instance->update->SetBounds = guac_rdp_gdi_set_bounds; + + primary = instance->update->primary; + primary->DstBlt = guac_rdp_gdi_dstblt; + primary->PatBlt = guac_rdp_gdi_patblt; + primary->ScrBlt = guac_rdp_gdi_scrblt; + primary->MemBlt = guac_rdp_gdi_memblt; + primary->OpaqueRect = guac_rdp_gdi_opaquerect; + + pointer_cache_register_callbacks(instance->update); + glyph_cache_register_callbacks(instance->update); + brush_cache_register_callbacks(instance->update); + bitmap_cache_register_callbacks(instance->update); + offscreen_cache_register_callbacks(instance->update); + palette_cache_register_callbacks(instance->update); + + /* Init channels (pre-connect) */ + if (freerdp_channels_pre_connect(channels, instance)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager"); + return FALSE; + } + + return TRUE; + +} + +BOOL rdp_freerdp_post_connect(freerdp* instance) { + + rdpContext* context = instance->context; + guac_client* client = ((rdp_freerdp_context*) context)->client; + rdpChannels* channels = instance->context->channels; + + /* Init channels (post-connect) */ + if (freerdp_channels_post_connect(channels, instance)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error initializing RDP client channel manager"); + return FALSE; + } + + return TRUE; + +} + +BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, + char** password, char** domain) { + + rdpContext* context = instance->context; + guac_client* client = ((rdp_freerdp_context*) context)->client; + + /* Warn if connection is likely to fail due to lack of credentials */ + guac_client_log(client, GUAC_LOG_INFO, + "Authentication requested but username or password not given"); + return TRUE; + +} + +BOOL rdp_freerdp_verify_certificate(freerdp* instance, char* subject, + char* issuer, char* fingerprint) { + + rdpContext* context = instance->context; + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = + (guac_rdp_client*) client->data; + + /* Bypass validation if ignore_certificate given */ + if (rdp_client->settings->ignore_certificate) { + guac_client_log(client, GUAC_LOG_INFO, "Certificate validation bypassed"); + return TRUE; + } + + guac_client_log(client, GUAC_LOG_INFO, "Certificate validation failed"); + return FALSE; + +} + +void rdp_freerdp_context_new(freerdp* instance, rdpContext* context) { + context->channels = freerdp_channels_new(); +} + +void rdp_freerdp_context_free(freerdp* instance, rdpContext* context) { + /* EMPTY */ +} + +void __guac_rdp_client_load_keymap(guac_client* client, + const guac_rdp_keymap* keymap) { + + guac_rdp_client* rdp_client = + (guac_rdp_client*) client->data; + + /* Get mapping */ + const guac_rdp_keysym_desc* mapping = keymap->mapping; + + /* If parent exists, load parent first */ + if (keymap->parent != NULL) + __guac_rdp_client_load_keymap(client, keymap->parent); + + /* Log load */ + guac_client_log(client, GUAC_LOG_INFO, "Loading keymap \"%s\"", keymap->name); + + /* Load mapping into keymap */ + while (mapping->keysym != 0) { + + /* Copy mapping */ + GUAC_RDP_KEYSYM_LOOKUP(rdp_client->keymap, mapping->keysym) = + *mapping; + + /* Next keysym */ + mapping++; + + } + +} + +/** + * Waits for messages from the RDP server for the given number of microseconds. + * + * @param client The client associated with the current RDP session. + * @param timeout_usecs The maximum amount of time to wait, in microseconds. + * + * @return + * A positive value if messages are ready, zero if the specified timeout + * period elapsed, or a negative value if an error occurs. + */ +static int rdp_guac_client_wait_for_messages(guac_client* client, + int timeout_usecs) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + freerdp* rdp_inst = rdp_client->rdp_inst; + rdpChannels* channels = rdp_inst->context->channels; + + int result; + int index; + int max_fd, fd; + void* read_fds[32]; + void* write_fds[32]; + int read_count = 0; + int write_count = 0; + fd_set rfds, wfds; + + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = timeout_usecs + }; + + /* Get RDP fds */ + if (!freerdp_get_fds(rdp_inst, read_fds, &read_count, write_fds, &write_count)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP file descriptors."); + return -1; + } + + /* Get channel fds */ + if (!freerdp_channels_get_fds(channels, rdp_inst, read_fds, &read_count, write_fds, + &write_count)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to read RDP channel file descriptors."); + return -1; + } + + /* Construct read fd_set */ + max_fd = 0; + FD_ZERO(&rfds); + for (index = 0; index < read_count; index++) { + fd = (int)(long) (read_fds[index]); + if (fd > max_fd) + max_fd = fd; + FD_SET(fd, &rfds); + } + + /* Construct write fd_set */ + FD_ZERO(&wfds); + for (index = 0; index < write_count; index++) { + fd = (int)(long) (write_fds[index]); + if (fd > max_fd) + max_fd = fd; + FD_SET(fd, &wfds); + } + + /* If no file descriptors, error */ + if (max_fd == 0) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "No file descriptors associated with RDP connection."); + return -1; + } + + /* Wait for all RDP file descriptors */ + result = select(max_fd + 1, &rfds, &wfds, NULL, &timeout); + if (result < 0) { + + /* If error ignorable, pretend timout occurred */ + if (errno == EAGAIN + || errno == EWOULDBLOCK + || errno == EINPROGRESS + || errno == EINTR) + return 0; + + /* Otherwise, return as error */ + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error waiting for file descriptor."); + return -1; + + } + + /* Return wait result */ + return result; + +} + +void* guac_rdp_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_settings* settings = rdp_client->settings; + + /* Init random number generator */ + srandom(time(NULL)); + + /* Create display */ + rdp_client->display = guac_common_display_alloc(client, + rdp_client->settings->width, + rdp_client->settings->height); + + rdp_client->current_surface = rdp_client->display->default_surface; + +#ifdef HAVE_FREERDP_CHANNELS_GLOBAL_INIT + freerdp_channels_global_init(); +#endif + + /* Init client */ + freerdp* rdp_inst = freerdp_new(); + rdp_inst->PreConnect = rdp_freerdp_pre_connect; + rdp_inst->PostConnect = rdp_freerdp_post_connect; + rdp_inst->Authenticate = rdp_freerdp_authenticate; + rdp_inst->VerifyCertificate = rdp_freerdp_verify_certificate; + rdp_inst->ReceiveChannelData = __guac_receive_channel_data; + + /* Allocate FreeRDP context */ +#ifdef LEGACY_FREERDP + rdp_inst->context_size = sizeof(rdp_freerdp_context); +#else + rdp_inst->ContextSize = sizeof(rdp_freerdp_context); +#endif + rdp_inst->ContextNew = (pContextNew) rdp_freerdp_context_new; + rdp_inst->ContextFree = (pContextFree) rdp_freerdp_context_free; + + freerdp_context_new(rdp_inst); + ((rdp_freerdp_context*) rdp_inst->context)->client = client; + + /* Load keymap into client */ + __guac_rdp_client_load_keymap(client, settings->server_layout); + +#ifdef ENABLE_COMMON_SSH + guac_common_ssh_init(client); + + /* Connect via SSH if SFTP is enabled */ + if (settings->enable_sftp) { + + /* Abort if username is missing */ + if (settings->sftp_username == NULL) + return NULL; + + guac_client_log(client, GUAC_LOG_DEBUG, + "Connecting via SSH for SFTP filesystem access."); + + rdp_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(rdp_client->sftp_user, + settings->sftp_private_key, + settings->sftp_passphrase)) { + guac_common_ssh_destroy_user(rdp_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(rdp_client->sftp_user, + settings->sftp_password); + + } + + /* Attempt SSH connection */ + rdp_client->sftp_session = + guac_common_ssh_create_session(client, settings->sftp_hostname, + settings->sftp_port, rdp_client->sftp_user); + + /* Fail if SSH connection does not succeed */ + if (rdp_client->sftp_session == NULL) { + /* Already aborted within guac_common_ssh_create_session() */ + guac_common_ssh_destroy_user(rdp_client->sftp_user); + return NULL; + } + + /* Load and expose filesystem */ + rdp_client->sftp_filesystem = + guac_common_ssh_create_sftp_filesystem( + rdp_client->sftp_session, "/"); + + /* Expose filesystem to connection owner */ + guac_client_for_owner(client, + guac_common_ssh_expose_sftp_filesystem, + rdp_client->sftp_filesystem); + + /* Abort if SFTP connection fails */ + if (rdp_client->sftp_filesystem == NULL) { + guac_common_ssh_destroy_session(rdp_client->sftp_session); + guac_common_ssh_destroy_user(rdp_client->sftp_user); + return NULL; + } + + guac_client_log(client, GUAC_LOG_DEBUG, + "SFTP connection succeeded."); + + } +#endif + + /* Send connection name */ + guac_protocol_send_name(client->socket, settings->hostname); + + /* Set default pointer */ + guac_common_cursor_set_pointer(rdp_client->display->cursor); + + /* Push desired settings to FreeRDP */ + guac_rdp_push_settings(settings, rdp_inst); + + /* Connect to RDP server */ + if (!freerdp_connect(rdp_inst)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Error connecting to RDP server"); + return NULL; + } + + /* Connection complete */ + rdp_client->rdp_inst = rdp_inst; + + rdpChannels* channels = rdp_inst->context->channels; + + /* Handle messages from RDP server while client is running */ + while (client->state == GUAC_CLIENT_RUNNING) { + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + /* Update remote display size */ + pthread_mutex_lock(&(rdp_client->rdp_lock)); + guac_rdp_disp_update_size(rdp_client->disp, rdp_inst->context); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); +#endif + + /* Wait for data and construct a reasonable frame */ + int wait_result = rdp_guac_client_wait_for_messages(client, 250000); + guac_timestamp frame_start = guac_timestamp_current(); + while (wait_result > 0) { + + guac_timestamp frame_end; + int frame_remaining; + + pthread_mutex_lock(&(rdp_client->rdp_lock)); + + /* Check the libfreerdp fds */ + if (!freerdp_check_fds(rdp_inst)) { + guac_client_log(client, GUAC_LOG_DEBUG, + "Error handling RDP file descriptors"); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + return NULL; + } + + /* Check channel fds */ + if (!freerdp_channels_check_fds(channels, rdp_inst)) { + guac_client_log(client, GUAC_LOG_DEBUG, + "Error handling RDP channel file descriptors"); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + return NULL; + } + + /* Check for channel events */ + wMessage* event = freerdp_channels_pop_event(channels); + if (event) { + + /* Handle channel events (clipboard and RAIL) */ +#ifdef LEGACY_EVENT + if (event->event_class == CliprdrChannel_Class) + guac_rdp_process_cliprdr_event(client, event); + else if (event->event_class == RailChannel_Class) + guac_rdp_process_rail_event(client, event); +#else + if (GetMessageClass(event->id) == CliprdrChannel_Class) + guac_rdp_process_cliprdr_event(client, event); + else if (GetMessageClass(event->id) == RailChannel_Class) + guac_rdp_process_rail_event(client, event); +#endif + + freerdp_event_free(event); + + } + + /* Handle RDP disconnect */ + if (freerdp_shall_disconnect(rdp_inst)) { + guac_client_log(client, GUAC_LOG_INFO, + "RDP server closed connection"); + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + return NULL; + } + + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + + /* Calculate time remaining in frame */ + frame_end = guac_timestamp_current(); + frame_remaining = frame_start + GUAC_RDP_FRAME_DURATION - frame_end; + + /* Wait again if frame remaining */ + if (frame_remaining > 0) + wait_result = rdp_guac_client_wait_for_messages(client, + GUAC_RDP_FRAME_TIMEOUT*1000); + else + break; + + } + + /* If an error occurred, fail */ + if (wait_result < 0) + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Connection closed."); + + /* End of frame */ + guac_common_display_flush(rdp_client->display); + guac_client_end_frame(client); + guac_socket_flush(client->socket); + + } + + guac_client_log(client, GUAC_LOG_INFO, "Internal RDP client disconnected"); + return NULL; + +} + diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h new file mode 100644 index 00000000..b648216c --- /dev/null +++ b/src/protocols/rdp/rdp.h @@ -0,0 +1,219 @@ +/* + * 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_RDP_H +#define GUAC_RDP_H + +#include "config.h" + +#include "guac_clipboard.h" +#include "guac_display.h" +#include "guac_surface.h" +#include "guac_list.h" +#include "rdp_fs.h" +#include "rdp_keymap.h" +#include "rdp_settings.h" + +#include +#include +#include +#include + +#ifdef ENABLE_COMMON_SSH +#include "guac_sftp.h" +#include "guac_ssh.h" +#include "guac_ssh_user.h" +#endif + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT +#include "rdp_disp.h" +#endif + +#include +#include + +/** + * RDP-specific client data. + */ +typedef struct guac_rdp_client { + + /** + * The RDP client thread. + */ + pthread_t client_thread; + + /** + * Pointer to the FreeRDP client instance handling the current connection. + */ + freerdp* rdp_inst; + + /** + * All settings associated with the current or pending RDP connection. + */ + guac_rdp_settings* settings; + + /** + * Button mask containing the OR'd value of all currently pressed buttons. + */ + int mouse_button_mask; + + /** + * Foreground color for any future glyphs. + */ + uint32_t glyph_color; + + /** + * The display. + */ + guac_common_display* display; + + /** + * The surface that GDI operations should draw to. RDP messages exist which + * change this surface to allow drawing to occur off-screen. + */ + guac_common_surface* current_surface; + + /** + * The keymap to use when translating keysyms into scancodes or sequences + * of scancodes for RDP. + */ + guac_rdp_static_keymap keymap; + + /** + * The state of all keys, based on whether events for pressing/releasing + * particular keysyms have been received. This is necessary in order to + * determine which keys must be released/pressed when a particular + * keysym can only be typed through a sequence of scancodes (such as + * an Alt-code) because the server-side keymap does not support that + * keysym. + */ + guac_rdp_keysym_state_map keysym_state; + + /** + * The current clipboard contents. + */ + guac_common_clipboard* clipboard; + + /** + * The format of the clipboard which was requested. Data received from + * the RDP server should conform to this format. This will be one of + * several legal clipboard format values defined within FreeRDP, such as + * CB_FORMAT_TEXT. + */ + int requested_clipboard_format; + + /** + * Audio output, if any. + */ + guac_audio_stream* audio; + + /** + * The filesystem being shared, if any. + */ + guac_rdp_fs* filesystem; + +#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 + +#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT + /** + * Display size update module. + */ + guac_rdp_disp* disp; +#endif + + /** + * List of all available static virtual channels. + */ + guac_common_list* available_svc; + + /** + * Lock which is locked and unlocked for each RDP message. + */ + pthread_mutex_t rdp_lock; + + /** + * Common attributes for locks. + */ + pthread_mutexattr_t attributes; + +} guac_rdp_client; + +/** + * Client data that will remain accessible through the RDP context. + * This should generally include data commonly used by FreeRDP handlers. + */ +typedef struct rdp_freerdp_context { + + /** + * The parent context. THIS MUST BE THE FIRST ELEMENT. + */ + rdpContext _p; + + /** + * Pointer to the guac_client instance handling the RDP connection with + * this context. + */ + guac_client* client; + + /** + * Color conversion structure to be used to convert RDP images to PNGs. + */ + CLRCONV* clrconv; + + /** + * The current color palette, as received from the RDP server. + */ + UINT32 palette[256]; + +} rdp_freerdp_context; + +/** + * RDP client thread. This thread runs throughout the duration of the client, + * existing as a single instance, shared by all users. + * + * @param data + * The guac_client to associate with an RDP session, once the RDP + * connection succeeds. + * + * @return + * NULL in all cases. The return value of this thread is expected to be + * ignored. + */ +void* guac_rdp_client_thread(void* data); + +#endif + diff --git a/src/protocols/rdp/rdp_bitmap.c b/src/protocols/rdp/rdp_bitmap.c index 8de2fcc1..f0c73c4d 100644 --- a/src/protocols/rdp/rdp_bitmap.c +++ b/src/protocols/rdp/rdp_bitmap.c @@ -23,7 +23,9 @@ #include "config.h" #include "client.h" +#include "guac_display.h" #include "guac_surface.h" +#include "rdp.h" #include "rdp_bitmap.h" #include "rdp_settings.h" @@ -46,12 +48,11 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_socket* socket = client->socket; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - /* Allocate surface */ - guac_layer* buffer = guac_client_alloc_buffer(client); - guac_common_surface* surface = guac_common_surface_alloc(client, socket, - buffer, bitmap->width, bitmap->height); + /* Allocate buffer */ + guac_common_display_layer* buffer = guac_common_display_alloc_buffer( + rdp_client->display, bitmap->width, bitmap->height); /* Cache image data if present */ if (bitmap->data != NULL) { @@ -62,7 +63,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) { bitmap->width, bitmap->height, 4*bitmap->width); /* Send surface to buffer */ - guac_common_surface_draw(surface, 0, 0, image); + guac_common_surface_draw(buffer->surface, 0, 0, image); /* Free surface */ cairo_surface_destroy(image); @@ -70,8 +71,7 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) { } /* Store buffer reference in bitmap */ - ((guac_rdp_bitmap*) bitmap)->buffer = buffer; - ((guac_rdp_bitmap*) bitmap)->surface = surface; + ((guac_rdp_bitmap*) bitmap)->layer = buffer; } @@ -101,8 +101,7 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { } /* No corresponding surface yet - caching is deferred. */ - ((guac_rdp_bitmap*) bitmap)->buffer = NULL; - ((guac_rdp_bitmap*) bitmap)->surface = NULL; + ((guac_rdp_bitmap*) bitmap)->layer = NULL; /* Start at zero usage */ ((guac_rdp_bitmap*) bitmap)->used = 0; @@ -112,21 +111,22 @@ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap) { void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) { guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_common_surface* surface = ((guac_rdp_bitmap*) bitmap)->surface; + guac_common_display_layer* buffer = ((guac_rdp_bitmap*) bitmap)->layer; int width = bitmap->right - bitmap->left + 1; int height = bitmap->bottom - bitmap->top + 1; /* If not cached, cache if necessary */ - if (surface == NULL && ((guac_rdp_bitmap*) bitmap)->used >= 1) + if (buffer == NULL && ((guac_rdp_bitmap*) bitmap)->used >= 1) guac_rdp_cache_bitmap(context, bitmap); /* If cached, retrieve from cache */ - if (surface != NULL) - guac_common_surface_copy(surface, 0, 0, width, height, - client_data->default_surface, bitmap->left, bitmap->top); + if (buffer != NULL) + guac_common_surface_copy(buffer->surface, 0, 0, width, height, + rdp_client->display->default_surface, + bitmap->left, bitmap->top); /* Otherwise, draw with stored image data */ else if (bitmap->data != NULL) { @@ -137,7 +137,8 @@ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) { width, height, 4*bitmap->width); /* Draw image on default surface */ - guac_common_surface_draw(client_data->default_surface, bitmap->left, bitmap->top, image); + guac_common_surface_draw(rdp_client->display->default_surface, + bitmap->left, bitmap->top, image); /* Free surface */ cairo_surface_destroy(image); @@ -152,26 +153,22 @@ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap) { void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_layer* buffer = ((guac_rdp_bitmap*) bitmap)->buffer; - guac_common_surface* surface = ((guac_rdp_bitmap*) bitmap)->surface; - - /* If cached, free surface */ - if (surface != NULL) - guac_common_surface_free(surface); + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_common_display_layer* buffer = ((guac_rdp_bitmap*) bitmap)->layer; /* If cached, free buffer */ if (buffer != NULL) - guac_client_free_buffer(client, buffer); + guac_common_display_free_buffer(rdp_client->display, buffer); } void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary) { guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; if (primary) - client_data->current_surface = client_data->default_surface; + rdp_client->current_surface = rdp_client->display->default_surface; else { @@ -182,10 +179,11 @@ void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL pri } /* If not available as a surface, make available. */ - if (((guac_rdp_bitmap*) bitmap)->surface == NULL) + if (((guac_rdp_bitmap*) bitmap)->layer == NULL) guac_rdp_cache_bitmap(context, bitmap); - client_data->current_surface = ((guac_rdp_bitmap*) bitmap)->surface; + rdp_client->current_surface = + ((guac_rdp_bitmap*) bitmap)->layer->surface; } diff --git a/src/protocols/rdp/rdp_bitmap.h b/src/protocols/rdp/rdp_bitmap.h index cab99a33..8bd28edd 100644 --- a/src/protocols/rdp/rdp_bitmap.h +++ b/src/protocols/rdp/rdp_bitmap.h @@ -25,7 +25,7 @@ #define _GUAC_RDP_RDP_BITMAP_H #include "config.h" -#include "guac_surface.h" +#include "guac_display.h" #include #include @@ -36,6 +36,9 @@ #include "compat/winpr-wtypes.h" #endif +/** + * Guacamole-specific rdpBitmap data. + */ typedef struct guac_rdp_bitmap { /** @@ -44,14 +47,9 @@ typedef struct guac_rdp_bitmap { rdpBitmap bitmap; /** - * The allocated buffer which backs this bitmap. + * Layer containing cached image data. */ - guac_layer* buffer; - - /** - * Surface containing cached image data. - */ - guac_common_surface* surface; + guac_common_display_layer* layer; /** * The number of times a bitmap has been used. @@ -60,18 +58,114 @@ typedef struct guac_rdp_bitmap { } guac_rdp_bitmap; +/** + * Caches the given bitmap immediately, storing its data in a remote Guacamole + * buffer. As RDP bitmaps are frequently created, used once, and immediately + * destroyed, we defer actual remote-side caching of RDP bitmaps until they are + * used at least once. + * + * @param context The rdpContext associated with the current RDP session. + * @param bitmap The bitmap to cache. + */ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap); + +/** + * Initializes the given newly-created rdpBitmap. + * + * @param context The rdpContext associated with the current RDP session. + * @param bitmap The bitmap to initialize. + */ void guac_rdp_bitmap_new(rdpContext* context, rdpBitmap* bitmap); + +/** + * Paints the given rdpBitmap on the primary display surface. Note that this + * operation does NOT draw to the "current" surface set by calls to + * guac_rdp_bitmap_setsurface(). + * + * @param context The rdpContext associated with the current RDP session. + * + * @param bitmap + * The bitmap to paint. This structure will also contain the specifics of + * the paint operation to perform, including the destination X/Y + * coordinates. + */ void guac_rdp_bitmap_paint(rdpContext* context, rdpBitmap* bitmap); + +/** + * Frees any Guacamole-specific data associated with the given rdpBitmap. + * + * @param context The rdpContext associated with the current RDP session. + * @param bitmap The bitmap whose Guacamole-specific data is to be freed. + */ void guac_rdp_bitmap_free(rdpContext* context, rdpBitmap* bitmap); -void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary); + +/** + * Sets the given rdpBitmap as the drawing surface for future operations or, + * if the primary flag is set, resets the current drawing surface to the + * primary drawing surface of the remote display. + * + * @param context The rdpContext associated with the current RDP session. + * + * @param bitmap + * The rdpBitmap to set as the current drawing surface. This parameter is + * only valid if the primary flag is FALSE. + * + * @param primary + * TRUE if the bitmap parameter should be ignored, and the current drawing + * surface should be reset to the primary drawing surface of the remote + * display, FALSE otherwise. + */ +void guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, + BOOL primary); #ifdef LEGACY_RDPBITMAP -void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, UINT8* data, - int width, int height, int bpp, int length, BOOL compressed); +/** + * Decompresses or copies the given image data, storing the result within the + * given bitmap, depending on the compressed flag. Note that even if the + * received data is not compressed, it is the duty of this function to also + * flip received data, if the row order is backwards. + * + * @param context The rdpContext associated with the current RDP session. + * + * @param bitmap + * The bitmap in which the decompressed/copied data should be stored. + * + * @param data Possibly-compressed image data. + * @param width The width of the image data, in pixels. + * @param height The height of the image data, in pixels. + * @param bpp The number of bits per pixel in the image data. + * @param length The length of the image data, in bytes. + * @param compressed TRUE if the image data is compressed, FALSE otherwise. + */ +void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, + UINT8* data, int width, int height, int bpp, int length, + BOOL compressed); #else -void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, UINT8* data, - int width, int height, int bpp, int length, BOOL compressed, int codec_id); +/** + * Decompresses or copies the given image data, storing the result within the + * given bitmap, depending on the compressed flag. Note that even if the + * received data is not compressed, it is the duty of this function to also + * flip received data, if the row order is backwards. + * + * @param context The rdpContext associated with the current RDP session. + * + * @param bitmap + * The bitmap in which the decompressed/copied data should be stored. + * + * @param data Possibly-compressed image data. + * @param width The width of the image data, in pixels. + * @param height The height of the image data, in pixels. + * @param bpp The number of bits per pixel in the image data. + * @param length The length of the image data, in bytes. + * @param compressed TRUE if the image data is compressed, FALSE otherwise. + * + * @param codec_id + * The ID of the codec used to compress the image data. This parameter is + * currently ignored. + */ +void guac_rdp_bitmap_decompress(rdpContext* context, rdpBitmap* bitmap, + UINT8* data, int width, int height, int bpp, int length, + BOOL compressed, int codec_id); #endif #endif diff --git a/src/protocols/rdp/rdp_cliprdr.c b/src/protocols/rdp/rdp_cliprdr.c index cf09a29a..fd14024f 100644 --- a/src/protocols/rdp/rdp_cliprdr.c +++ b/src/protocols/rdp/rdp_cliprdr.c @@ -23,6 +23,7 @@ #include "config.h" #include "client.h" +#include "rdp.h" #include "rdp_cliprdr.h" #include "guac_clipboard.h" #include "guac_iconv.h" @@ -92,7 +93,7 @@ void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event) { void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event) { rdpChannels* channels = - ((rdp_guac_client_data*) client->data)->rdp_inst->context->channels; + ((guac_rdp_client*) client->data)->rdp_inst->context->channels; RDP_CB_FORMAT_LIST_EVENT* format_list = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new( @@ -114,11 +115,16 @@ void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event) { /** * Sends a clipboard data request for the given format. + * + * @param client The guac_client associated with the current RDP session. + * @param format + * The clipboard format to request. This format must be one of the + * documented values used by the CLIPRDR channel for clipboard format IDs. */ static void __guac_rdp_cb_request_format(guac_client* client, int format) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; - rdpChannels* channels = client_data->rdp_inst->context->channels; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + rdpChannels* channels = rdp_client->rdp_inst->context->channels; /* Create new data request */ RDP_CB_DATA_REQUEST_EVENT* data_request = @@ -128,7 +134,7 @@ static void __guac_rdp_cb_request_format(guac_client* client, int format) { NULL, NULL); /* Set to requested format */ - client_data->requested_clipboard_format = format; + rdp_client->requested_clipboard_format = format; data_request->format = format; /* Send request */ @@ -174,11 +180,11 @@ void guac_rdp_process_cb_format_list(guac_client* client, void guac_rdp_process_cb_data_request(guac_client* client, RDP_CB_DATA_REQUEST_EVENT* event) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; - rdpChannels* channels = client_data->rdp_inst->context->channels; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + rdpChannels* channels = rdp_client->rdp_inst->context->channels; guac_iconv_write* writer; - const char* input = client_data->clipboard->buffer; + const char* input = rdp_client->clipboard->buffer; char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); RDP_CB_DATA_RESPONSE_EVENT* data_response; @@ -209,7 +215,7 @@ void guac_rdp_process_cb_data_request(guac_client* client, /* Set data and size */ data_response->data = (BYTE*) output; - guac_iconv(GUAC_READ_UTF8, &input, client_data->clipboard->length, + guac_iconv(GUAC_READ_UTF8, &input, rdp_client->clipboard->length, writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH); data_response->size = ((BYTE*) output) - data_response->data; @@ -221,7 +227,7 @@ void guac_rdp_process_cb_data_request(guac_client* client, void guac_rdp_process_cb_data_response(guac_client* client, RDP_CB_DATA_RESPONSE_EVENT* event) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH]; guac_iconv_read* reader; @@ -229,7 +235,7 @@ void guac_rdp_process_cb_data_response(guac_client* client, char* output = received_data; /* Find correct source encoding */ - switch (client_data->requested_clipboard_format) { + switch (rdp_client->requested_clipboard_format) { /* Non-Unicode */ case CB_FORMAT_TEXT: @@ -244,7 +250,7 @@ void guac_rdp_process_cb_data_response(guac_client* client, default: guac_client_log(client, GUAC_LOG_ERROR, "Requested clipboard data in " "unsupported format %i", - client_data->requested_clipboard_format); + rdp_client->requested_clipboard_format); return; } @@ -254,9 +260,9 @@ void guac_rdp_process_cb_data_response(guac_client* client, GUAC_WRITE_UTF8, &output, sizeof(received_data))) { int length = strnlen(received_data, sizeof(received_data)); - guac_common_clipboard_reset(client_data->clipboard, "text/plain"); - guac_common_clipboard_append(client_data->clipboard, received_data, length); - guac_common_clipboard_send(client_data->clipboard, client); + guac_common_clipboard_reset(rdp_client->clipboard, "text/plain"); + guac_common_clipboard_append(rdp_client->clipboard, received_data, length); + guac_common_clipboard_send(rdp_client->clipboard, client); } diff --git a/src/protocols/rdp/rdp_cliprdr.h b/src/protocols/rdp/rdp_cliprdr.h index e8ed53f9..ae2d36eb 100644 --- a/src/protocols/rdp/rdp_cliprdr.h +++ b/src/protocols/rdp/rdp_cliprdr.h @@ -50,15 +50,65 @@ */ #define GUAC_RDP_CLIPBOARD_FORMAT_UTF16 2 +/** + * Called within the main RDP connection thread whenever a CLIPRDR message is + * received. This function will dispatch that message to an appropriate + * function, specific to that message type. + * + * @param client The guac_client associated with the current RDP session. + * @param event The received CLIPRDR message. + */ void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event); + +/** + * Handles the given CLIPRDR event, which MUST be a Monitor Ready event. It + * is the responsibility of this function to respond to the Monitor Ready + * event with a list of supported clipboard formats. + * + * @param client The guac_client associated with the current RDP session. + * + * @param event + * The received CLIPRDR message, which must be a Monitor Ready event. + */ void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event); +/** + * Handles the given CLIPRDR event, which MUST be a Format List event. It + * is the responsibility of this function to respond to the Format List + * event with a request for clipboard data in one of the enumerated formats. + * This event is fired whenever remote clipboard data is available. + * + * @param client The guac_client associated with the current RDP session. + * + * @param event + * The received CLIPRDR message, which must be a Format List event. + */ void guac_rdp_process_cb_format_list(guac_client* client, RDP_CB_FORMAT_LIST_EVENT* event); +/** + * Handles the given CLIPRDR event, which MUST be a Data Request event. It + * is the responsibility of this function to respond to the Data Request + * event with a data response containing the current clipoard contents. + * + * @param client The guac_client associated with the current RDP session. + * + * @param event + * The received CLIPRDR message, which must be a Data Request event. + */ void guac_rdp_process_cb_data_request(guac_client* client, RDP_CB_DATA_REQUEST_EVENT* event); +/** + * Handles the given CLIPRDR event, which MUST be a Data Response event. It + * is the responsibility of this function to read and forward the received + * clipboard data to connected clients. + * + * @param client The guac_client associated with the current RDP session. + * + * @param event + * The received CLIPRDR message, which must be a Data Response event. + */ void guac_rdp_process_cb_data_response(guac_client* client, RDP_CB_DATA_RESPONSE_EVENT* event); diff --git a/src/protocols/rdp/rdp_color.c b/src/protocols/rdp/rdp_color.c index 9828d3b1..33212656 100644 --- a/src/protocols/rdp/rdp_color.c +++ b/src/protocols/rdp/rdp_color.c @@ -23,6 +23,7 @@ #include "config.h" #include "client.h" +#include "rdp.h" #include "rdp_settings.h" #include diff --git a/src/protocols/rdp/rdp_color.h b/src/protocols/rdp/rdp_color.h index 6c47450b..fb473333 100644 --- a/src/protocols/rdp/rdp_color.h +++ b/src/protocols/rdp/rdp_color.h @@ -35,6 +35,12 @@ * Converts the given color to ARGB32. The color given may be an index * referring to the palette, a 16-bit or 32-bit color, etc. all depending on * the current color depth of the RDP session. + * + * @param context The rdpContext associated with the current RDP session. + * @param color A color value in the format of the current RDP session. + * @return + * A 32-bit ARGB color, where the low 8 bits are the blue component and + * the high 8 bits are alpha. */ UINT32 guac_rdp_convert_color(rdpContext* context, UINT32 color); diff --git a/src/protocols/rdp/rdp_disp.c b/src/protocols/rdp/rdp_disp.c index f13e092a..a94d5949 100644 --- a/src/protocols/rdp/rdp_disp.c +++ b/src/protocols/rdp/rdp_disp.c @@ -22,6 +22,8 @@ #include "config.h" #include "client.h" +#include "rdp.h" +#include "rdp_disp.h" #include #include diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index e6ccbdf8..2481082f 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -38,8 +38,11 @@ #include #include +#include #include #include +#include +#include guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, int create_drive_path) { @@ -61,10 +64,6 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, guac_rdp_fs* fs = malloc(sizeof(guac_rdp_fs)); fs->client = client; - fs->object = guac_client_alloc_object(client); - fs->object->get_handler = guac_rdp_download_get_handler; - fs->object->put_handler = guac_rdp_upload_put_handler; - fs->drive_path = strdup(drive_path); fs->file_id_pool = guac_pool_alloc(0); fs->open_files = 0; @@ -74,16 +73,55 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, } void guac_rdp_fs_free(guac_rdp_fs* fs) { - guac_client_free_object(fs->client, fs->object); guac_pool_free(fs->file_id_pool); free(fs->drive_path); free(fs); } +guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user) { + + /* Init filesystem */ + guac_object* fs_object = guac_user_alloc_object(user); + fs_object->get_handler = guac_rdp_download_get_handler; + fs_object->put_handler = guac_rdp_upload_put_handler; + fs_object->data = fs; + + /* Send filesystem to user */ + guac_protocol_send_filesystem(user->socket, fs_object, "Shared Drive"); + guac_socket_flush(user->socket); + + return fs_object; + +} + +void* guac_rdp_fs_expose(guac_user* user, void* data) { + + guac_rdp_fs* fs = (guac_rdp_fs*) data; + + /* No need to expose if there is no filesystem or the user has left */ + if (user == NULL || fs == NULL) + return NULL; + + /* Allocate and expose filesystem object for user */ + return guac_rdp_fs_alloc_object(fs, user); + +} + /** - * Translates an absolute Windows virtual_path to an absolute virtual_path - * which is within the "drive virtual_path" specified in the connection - * settings. + * Translates an absolute Windows path to an absolute path which is within the + * "drive path" specified in the connection settings. No checking is performed + * on the path provided, which is assumed to have already been normalized and + * validated as absolute. + * + * @param fs The filesystem containing the file whose path is being translated. + * + * @param virtual_path + * The absolute path to the file on the simulated filesystem, relative to + * the simulated filesystem root. + * + * @param real_path + * The buffer in which to store the absolute path to the real file on the + * local filesystem. */ static void __guac_rdp_fs_translate_path(guac_rdp_fs* fs, const char* virtual_path, char* real_path) { @@ -701,7 +739,7 @@ int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info) { /* Read FS information */ struct statvfs fs_stat; if (statvfs(fs->drive_path, &fs_stat)) - return guac_rdp_fs_get_status(errno); + return guac_rdp_fs_get_errorcode(errno); /* Assign to structure */ info->blocks_available = fs_stat.f_bfree; diff --git a/src/protocols/rdp/rdp_fs.h b/src/protocols/rdp/rdp_fs.h index 48cf6eab..5a7f7567 100644 --- a/src/protocols/rdp/rdp_fs.h +++ b/src/protocols/rdp/rdp_fs.h @@ -38,7 +38,6 @@ #include "config.h" #include -#include #include #include @@ -269,15 +268,10 @@ typedef struct guac_rdp_fs_file { typedef struct guac_rdp_fs { /** - * The controlling client. + * The Guacamole client associated with the RDP session. */ guac_client* client; - /** - * The underlying filesystem object. - */ - guac_object* object; - /** * The root of the filesystem. */ @@ -323,34 +317,145 @@ typedef struct guac_rdp_fs_info { } guac_rdp_fs_info; /** - * Allocates a new filesystem given a root path. + * Allocates a new filesystem given a root path. This filesystem will behave + * as if it were a network drive. + * + * @param client + * The guac_client associated with the current RDP session. + * + * @param drive_path + * The local directory to use as the root directory of the emulated + * network drive. + * + * @param create_drive_path + * Non-zero if the drive path specified should be automatically created if + * it does not yet exist, zero otherwise. + * + * @return + * The newly-allocated filesystem. */ -guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, int create_drive_path); +guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, + int create_drive_path); /** * Frees the given filesystem. + * + * @param fs The filesystem to free. */ void guac_rdp_fs_free(guac_rdp_fs* fs); +/** + * Creates and exposes a new filesystem guac_object to the given user, + * providing access to the files within the given RDP filesystem. The + * allocated guac_object must eventually be freed via guac_user_free_object(). + * + * @param fs + * The RDP filesystem object to expose. + * + * @param user + * The user that the RDP filesystem should be exposed to. + * + * @return + * A new Guacamole filesystem object, configured to use RDP for uploading + * and downloading files. + */ +guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user); + +/** + * Allocates a new filesystem guac_object for the given user, returning the + * resulting guac_object. This function is provided for convenience, as it is + * can be used as the callback for guac_client_foreach_user() or + * guac_client_for_owner(). Note that this guac_object will be tracked + * internally by libguac, will be provided to us in the parameters of handlers + * related to that guac_object, and will automatically be freed when the + * associated guac_user is freed, so the return value of this function can + * safely be ignored. + * + * If either the given user or the given filesystem are NULL, then this + * function has no effect. + * + * @param user + * The use to expose the filesystem to, or NULL if nothing should be + * exposed. + * + * @param data + * A pointer to the guac_rdp_fs instance to expose to the given user, or + * NULL if nothing should be exposed. + * + * @return + * The guac_object allocated for the newly-exposed filesystem, or NULL if + * no filesystem object could be allocated. + */ +void* guac_rdp_fs_expose(guac_user* user, void* data); + /** * Converts the given relative path to an absolute path based on the given * parent path. If the path cannot be converted, non-zero is returned. + * + * @param parent The parent directory of the relative path. + * @param rel_path The relative path to convert. + * @return Zero if the path was converted successfully, non-zero otherwise. */ -int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path); +int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, + char* abs_path); /** * Translates the given errno error code to a GUAC_RDP_FS error code. + * + * @param err The error code, as returned within errno by a system call. + * + * @return + * A GUAC_RDP_FS error code, such as GUAC_RDP_FS_ENFILE, + * GUAC_RDP_FS_ENOENT, etc. */ int guac_rdp_fs_get_errorcode(int err); /** - * Teanslates the given GUAC_RDP_FS error code to an RDPDR status code. + * Translates the given GUAC_RDP_FS error code to an RDPDR status code. + * + * @param err + * A GUAC_RDP_FS error code, such as GUAC_RDP_FS_ENFILE, + * GUAC_RDP_FS_ENOENT, etc. + * + * @return + * A status code corresponding to the given error code that an + * implementation of the RDPDR channel can understand. */ int guac_rdp_fs_get_status(int err); /** - * Returns the next available file ID, or an error code less than zero - * if an error occurs. + * Opens the given file, returning the a new file ID, or an error code less + * than zero if an error occurs. The given path MUST be absolute, and will be + * translated to be relative to the drive path of the simulated filesystem. + * + * @param fs The filesystem to use when opening the file. + * @param path The absolute path to the file within the simulated filesystem. + * + * @param access + * A bitwise-OR of various RDPDR access flags, such as ACCESS_GENERIC_ALL + * or ACCESS_GENERIC_WRITE. This value will ultimately be translated to a + * standard O_RDWR, O_WRONLY, etc. value when opening the real file on the + * local filesystem. + * + * @param file_attributes + * The attributes to apply to the file, if created. This parameter is + * currently ignored, and has no effect. + * + * @param create_disposition + * Any one of several RDPDR file creation dispositions, such as + * DISP_FILE_CREATE, DISP_FILE_OPEN_IF, etc. The creation disposition + * dictates whether a new file should be created, whether the file can + * already exist, whether existing contents should be truncated, etc. + * + * @param create_options + * A bitwise-OR of various RDPDR options dictating how a file is to be + * created. Currently only one option is implemented, FILE_DIRECTORY_FILE, + * which specifies that the new file must be a directory. + * + * @return + * A new file ID, which will always be a positive value, or an error code + * if an error occurs. All error codes are negative values and correspond + * to GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT. */ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, int access, int file_attributes, int create_disposition, @@ -360,6 +465,20 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, * Reads up to the given length of bytes from the given offset within the * file having the given ID. Returns the number of bytes read, zero on EOF, * and an error code if an error occurs. + * + * @param fs The filesystem containing the file from which data is to be read. + * + * @param file_id + * The ID of the file to read data from, as returned by guac_rdp_fs_open(). + * + * @param offset The byte offset within the file to start reading from. + * @param buffer The buffer to fill with data from the file. + * @param length The maximum number of bytes to read from the file. + * + * @return + * The number of bytes actually read, zero on EOF, or an error code if an + * error occurs. All error codes are negative values and correspond to + * GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT. */ int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, int offset, void* buffer, int length); @@ -368,6 +487,20 @@ int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, int offset, * Writes up to the given length of bytes from the given offset within the * file having the given ID. Returns the number of bytes written, and an * error code if an error occurs. + * + * @param fs The filesystem containing the file to which data is to be written. + * + * @param file_id + * The ID of the file to write data to, as returned by guac_rdp_fs_open(). + * + * @param offset The byte offset within the file to start writinging at. + * @param buffer The buffer containing the data to write. + * @param length The maximum number of bytes to write to the file. + * + * @return + * The number of bytes actually written, or an error code if an error + * occurs. All error codes are negative values and correspond to + * GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT. */ int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, int offset, void* buffer, int length); @@ -375,56 +508,148 @@ int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, int offset, /** * Renames (moves) the file with the given ID to the new path specified. * Returns zero on success, or an error code if an error occurs. + * + * @param fs The filesystem containing the file to rename. + * + * @param file_id + * The ID of the file to rename, as returned by guac_rdp_fs_open(). + * + * @param new_path The absolute path to move the file to. + * + * @return + * Zero if the rename succeeded, or an error code if an error occurs. All + * error codes are negative values and correspond to GUAC_RDP_FS constants, + * such as GUAC_RDP_FS_ENOENT. */ int guac_rdp_fs_rename(guac_rdp_fs* fs, int file_id, const char* new_path); /** * Deletes the file with the given ID. + * + * @param fs The filesystem containing the file to delete. + * + * @param file_id + * The ID of the file to delete, as returned by guac_rdp_fs_open(). + * + * @return + * Zero if deletion succeeded, or an error code if an error occurs. All + * error codes are negative values and correspond to GUAC_RDP_FS constants, + * such as GUAC_RDP_FS_ENOENT. */ int guac_rdp_fs_delete(guac_rdp_fs* fs, int file_id); /** * Truncates the file with the given ID to the given length (in bytes), which * may be larger. + * + * @param fs The filesystem containing the file to truncate. + * + * @param file_id + * The ID of the file to truncate, as returned by guac_rdp_fs_open(). + * + * @param length + * The new length of the file, in bytes. Despite being named "truncate", + * this new length may be larger. + * + * @return + * Zero if truncation succeeded, or an error code if an error occurs. All + * error codes are negative values and correspond to GUAC_RDP_FS constants, + * such as GUAC_RDP_FS_ENOENT. */ int guac_rdp_fs_truncate(guac_rdp_fs* fs, int file_id, int length); /** * Frees the given file ID, allowing future open operations to reuse it. + * + * @param fs The filesystem containing the file to close. + * + * @param file_id + * The ID of the file to close, as returned by guac_rdp_fs_open(). */ void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id); /** * Given an arbitrary path, which may contain ".." and ".", creates an - * absolute path which does NOT contain ".." or ".". + * absolute path which does NOT contain ".." or ".". The given path MUST + * be absolute. + * + * @param path The absolute path to normalize. + * @param abs_path + * The buffer to populate with the normalized path. The normalized path + * will not contain relative path components like ".." or ".". + * + * @return Zero if normalization succeeded, non-zero otherwise. */ int guac_rdp_fs_normalize_path(const char* path, char* abs_path); /** - * Given a parent path and a relative path, produces a normalized absolute path. + * Given a parent path and a relative path, produces a normalized absolute + * path. + * + * @param parent + * The absolute path of the parent directory of the relative path. + * + * @param rel_path The relative path to convert. + * + * @param abs_path + * The buffer to populate with the absolute, normalized path. The + * normalized path will not contain relative path components like ".." or + * ".". + * + * @return Zero if conversion succeeded, non-zero otherwise. */ -int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path); +int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, + char* abs_path); /** * Returns the next filename within the directory having the given file ID, * or NULL if no more files. + * + * @param fs The filesystem containing the file to read directory entries from. + * + * @param file_id + * The ID of the file to read directory entries from, as returned by + * guac_rdp_fs_open(). + * + * @return + * The name of the next filename within the directory, or NULL if the last + * file in the directory has already been returned by a previous call. */ const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id); /** * Returns the file having the given ID, or NULL if no such file exists. + * + * @param fs The filesystem containing the desired file. + * @param file_id The ID of the desired, as returned by guac_rdp_fs_open(). + * @return The file having the given ID, or NULL is no such file exists. */ guac_rdp_fs_file* guac_rdp_fs_get_file(guac_rdp_fs* fs, int file_id); /** - * Returns whether the given filename matches the given pattern. + * Returns whether the given filename matches the given pattern. The pattern + * given is a shell wildcard pattern as accepted by the POSIX fnmatch() + * function. Backslashes will be interpreted as literal backslashes, not + * escape characters. + * + * @param filename The filename to check + * @param pattern The pattern to check the filename against. + * @return Non-zero if the pattern matches, zero otherwise. */ int guac_rdp_fs_matches(const char* filename, const char* pattern); /** * Populates the given structure with information about the filesystem, * particularly the amount of space available. + * + * @param fs The filesystem to obtain information from. + * @param info The guac_rdp_fs_info structure to populate. + * + * @return + * Zero if information retrieval succeeded, or an error code if an error + * occurs. All error codes are negative values and correspond to + * GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT. */ int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info); diff --git a/src/protocols/rdp/rdp_gdi.c b/src/protocols/rdp/rdp_gdi.c index 74eb5ee2..32f67861 100644 --- a/src/protocols/rdp/rdp_gdi.c +++ b/src/protocols/rdp/rdp_gdi.c @@ -24,6 +24,7 @@ #include "client.h" #include "guac_surface.h" +#include "rdp.h" #include "rdp_bitmap.h" #include "rdp_color.h" #include "rdp_settings.h" @@ -102,7 +103,7 @@ guac_transfer_function guac_rdp_rop3_transfer_function(guac_client* client, void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface; + guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface; int x = dstblt->nLeftRect; int y = dstblt->nTopRect; @@ -158,7 +159,7 @@ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) { /* Get client and current layer */ guac_client* client = ((rdp_freerdp_context*) context)->client; guac_common_surface* current_surface = - ((rdp_guac_client_data*) client->data)->current_surface; + ((guac_rdp_client*) client->data)->current_surface; int x = patblt->nLeftRect; int y = patblt->nTopRect; @@ -210,7 +211,7 @@ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) { void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface; + guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface; int x = scrblt->nLeftRect; int y = scrblt->nTopRect; @@ -220,18 +221,18 @@ void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt) { int x_src = scrblt->nXSrc; int y_src = scrblt->nYSrc; - rdp_guac_client_data* data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Copy screen rect to current surface */ - guac_common_surface_copy(data->default_surface, x_src, y_src, w, h, - current_surface, x, y); + guac_common_surface_copy(rdp_client->display->default_surface, + x_src, y_src, w, h, current_surface, x, y); } void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface; + guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface; guac_rdp_bitmap* bitmap = (guac_rdp_bitmap*) memblt->bitmap; int x = memblt->nLeftRect; @@ -263,11 +264,11 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { case 0xCC: /* If not cached, cache if necessary */ - if (bitmap->surface == NULL && bitmap->used >= 1) + if (bitmap->layer == NULL && bitmap->used >= 1) guac_rdp_cache_bitmap(context, memblt->bitmap); /* If not cached, send as PNG */ - if (bitmap->surface == NULL) { + if (bitmap->layer == NULL) { if (memblt->bitmap->data != NULL) { /* Create surface from image data */ @@ -286,8 +287,8 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { /* Otherwise, copy */ else - guac_common_surface_copy(bitmap->surface, x_src, y_src, w, h, - current_surface, x, y); + guac_common_surface_copy(bitmap->layer->surface, + x_src, y_src, w, h, current_surface, x, y); /* Increment usage counter */ ((guac_rdp_bitmap*) bitmap)->used++; @@ -303,12 +304,13 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { default: /* If not available as a surface, make available. */ - if (bitmap->surface == NULL) + if (bitmap->layer == NULL) guac_rdp_cache_bitmap(context, memblt->bitmap); - guac_common_surface_transfer(bitmap->surface, x_src, y_src, w, h, - guac_rdp_rop3_transfer_function(client, memblt->bRop), - current_surface, x, y); + guac_common_surface_transfer(bitmap->layer->surface, + x_src, y_src, w, h, + guac_rdp_rop3_transfer_function(client, memblt->bRop), + current_surface, x, y); /* Increment usage counter */ ((guac_rdp_bitmap*) bitmap)->used++; @@ -324,7 +326,7 @@ void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect UINT32 color = guac_rdp_convert_color(context, opaque_rect->color); - guac_common_surface* current_surface = ((rdp_guac_client_data*) client->data)->current_surface; + guac_common_surface* current_surface = ((guac_rdp_client*) client->data)->current_surface; int x = opaque_rect->nLeftRect; int y = opaque_rect->nTopRect; @@ -341,6 +343,12 @@ void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect /** * Updates the palette within a FreeRDP CLRCONV object using the new palette * entries provided by an RDP palette update. + * + * @param clrconv The FreeRDP CLRCONV object to update. + * + * @param palette + * An RDP palette update message containing the palette to store within the + * given CLRCONV object. */ static void guac_rdp_update_clrconv(CLRCONV* clrconv, PALETTE_UPDATE* palette) { @@ -358,6 +366,14 @@ static void guac_rdp_update_clrconv(CLRCONV* clrconv, /** * Updates a raw ARGB32 palette using the new palette entries provided by an * RDP palette update. + * + * @param guac_palette + * An array of 256 ARGB32 colors, with each entry corresponding to an + * entry in the color palette. + * + * @param palette + * An RDP palette update message containing the palette to store within the + * given array of ARGB32 colors. */ static void guac_rdp_update_palette(UINT32* guac_palette, PALETTE_UPDATE* palette) { @@ -393,16 +409,18 @@ void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette) { void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds) { guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* If no bounds given, clear bounding rect */ if (bounds == NULL) - guac_common_surface_reset_clip(data->default_surface); + guac_common_surface_reset_clip(rdp_client->display->default_surface); /* Otherwise, set bounding rectangle */ else - guac_common_surface_clip(data->default_surface, bounds->left, bounds->top, - bounds->right - bounds->left + 1, bounds->bottom - bounds->top + 1); + guac_common_surface_clip(rdp_client->display->default_surface, + bounds->left, bounds->top, + bounds->right - bounds->left + 1, + bounds->bottom - bounds->top + 1); } @@ -413,13 +431,13 @@ void guac_rdp_gdi_end_paint(rdpContext* context) { void guac_rdp_gdi_desktop_resize(rdpContext* context) { guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_common_surface_resize(data->default_surface, + guac_common_surface_resize(rdp_client->display->default_surface, guac_rdp_get_width(context->instance), guac_rdp_get_height(context->instance)); - guac_common_surface_reset_clip(data->default_surface); + guac_common_surface_reset_clip(rdp_client->display->default_surface); guac_client_log(client, GUAC_LOG_DEBUG, "Server resized display to %ix%i", guac_rdp_get_width(context->instance), diff --git a/src/protocols/rdp/rdp_gdi.h b/src/protocols/rdp/rdp_gdi.h index 9520c0f1..664dc38b 100644 --- a/src/protocols/rdp/rdp_gdi.h +++ b/src/protocols/rdp/rdp_gdi.h @@ -30,49 +30,89 @@ #include /** - * Translates a standard RDP ROP3 value into a guac_composite_mode. + * Translates a standard RDP ROP3 value into a guac_composite_mode. Valid + * ROP3 operations indexes are listed in the RDP protocol specifications: + * + * http://msdn.microsoft.com/en-us/library/cc241583.aspx + * + * @param client The guac_client associated with the current RDP session. + * @param rop3 The ROP3 operation index to translate. + * + * @return + * The guac_composite_mode that equates to, or most closely approximates, + * the given ROP3 operation. */ -guac_composite_mode guac_rdp_rop3_transfer_function(guac_client* client, int rop3); +guac_composite_mode guac_rdp_rop3_transfer_function(guac_client* client, + int rop3); /** * Handler for RDP DSTBLT update. + * + * @param context The rdpContext associated with the current RDP session. + * @param dstblt The DSTBLT update to handle. */ void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt); /** * Handler for RDP PATBLT update. + * + * @param context The rdpContext associated with the current RDP session. + * @param patblt The PATBLT update to handle. */ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt); /** * Handler for RDP SCRBLT update. + * + * @param context The rdpContext associated with the current RDP session. + * @param scrblt The SCRBLT update to handle. */ void guac_rdp_gdi_scrblt(rdpContext* context, SCRBLT_ORDER* scrblt); /** * Handler for RDP MEMBLT update. + * + * @param context The rdpContext associated with the current RDP session. + * @param memblt The MEMBLT update to handle. */ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt); /** - * Handler for RDP OPAQUE_RECT update. + * Handler for RDP OPAQUE RECT update. + * + * @param context The rdpContext associated with the current RDP session. + * @param opaque_rect The OPAQUE RECT update to handle. */ -void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect); +void guac_rdp_gdi_opaquerect(rdpContext* context, + OPAQUE_RECT_ORDER* opaque_rect); /** * Handler called when the remote color palette is changing. + * + * @param context The rdpContext associated with the current RDP session. + * @param palette The PALETTE update containing the new palette. */ void guac_rdp_gdi_palette_update(rdpContext* context, PALETTE_UPDATE* palette); /** * Handler called prior to calling the handlers for specific updates when - * those updatese are clipped by a bounding rectangle. + * those updates are clipped by a bounding rectangle. This is not a true RDP + * update, but is called by FreeRDP before and after any update involving + * clipping. + * + * @param context The rdpContext associated with the current RDP session. + * + * @param bounds + * The clipping rectangle to set, or NULL to remove any applied clipping + * rectangle. */ void guac_rdp_gdi_set_bounds(rdpContext* context, rdpBounds* bounds); /** * Handler called when a paint operation is complete. We don't actually - * use this, but FreeRDP requires it. + * use this, but FreeRDP requires it. Calling this function has no effect. + * + * @param context The rdpContext associated with the current RDP session. */ void guac_rdp_gdi_end_paint(rdpContext* context); @@ -81,6 +121,11 @@ void guac_rdp_gdi_end_paint(rdpContext* context); * true desktop resize event received by the RDP client, or due to * a revised size given by the server during initial connection * negotiation. + * + * The new screen size will be made available within the settings associated + * with the given context. + * + * @param context The rdpContext associated with the current RDP session. */ void guac_rdp_gdi_desktop_resize(rdpContext* context); diff --git a/src/protocols/rdp/rdp_glyph.c b/src/protocols/rdp/rdp_glyph.c index 735d2ed0..eaf43315 100644 --- a/src/protocols/rdp/rdp_glyph.c +++ b/src/protocols/rdp/rdp_glyph.c @@ -24,6 +24,7 @@ #include "client.h" #include "guac_surface.h" +#include "rdp.h" #include "rdp_color.h" #include "rdp_glyph.h" #include "rdp_settings.h" @@ -102,9 +103,9 @@ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph) { void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y) { guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* guac_client_data = (rdp_guac_client_data*) client->data; - guac_common_surface* current_surface = guac_client_data->current_surface; - uint32_t fgcolor = guac_client_data->glyph_color; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_common_surface* current_surface = rdp_client->current_surface; + uint32_t fgcolor = rdp_client->glyph_color; /* Paint with glyph as mask */ guac_common_surface_paint(current_surface, x, y, ((guac_rdp_glyph*) glyph)->surface, @@ -129,8 +130,8 @@ void guac_rdp_glyph_begindraw(rdpContext* context, int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor) { guac_client* client = ((rdp_freerdp_context*) context)->client; - rdp_guac_client_data* guac_client_data = - (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = + (guac_rdp_client*) client->data; /* Fill background with color if specified */ if (width != 0 && height != 0) { @@ -138,7 +139,7 @@ void guac_rdp_glyph_begindraw(rdpContext* context, /* Convert background color */ bgcolor = guac_rdp_convert_color(context, bgcolor); - guac_common_surface_rect(guac_client_data->current_surface, x, y, width, height, + guac_common_surface_rect(rdp_client->current_surface, x, y, width, height, (bgcolor & 0xFF0000) >> 16, (bgcolor & 0x00FF00) >> 8, bgcolor & 0x0000FF); @@ -146,7 +147,7 @@ void guac_rdp_glyph_begindraw(rdpContext* context, } /* Convert foreground color */ - guac_client_data->glyph_color = guac_rdp_convert_color(context, fgcolor); + rdp_client->glyph_color = guac_rdp_convert_color(context, fgcolor); } diff --git a/src/protocols/rdp/rdp_glyph.h b/src/protocols/rdp/rdp_glyph.h index b7a7f153..3b2df136 100644 --- a/src/protocols/rdp/rdp_glyph.h +++ b/src/protocols/rdp/rdp_glyph.h @@ -35,6 +35,9 @@ #include "compat/winpr-wtypes.h" #endif +/** + * Guacamole-specific rdpGlyph data. + */ typedef struct guac_rdp_glyph { /** @@ -49,11 +52,105 @@ typedef struct guac_rdp_glyph { } guac_rdp_glyph; +/** + * Caches the given glyph. Note that this caching currently only occurs server- + * side, as it is more efficient to transmit the text as PNG. + * + * @param context The rdpContext associated with the current RDP session. + * @param glyph The glyph to cache. + */ void guac_rdp_glyph_new(rdpContext* context, rdpGlyph* glyph); + +/** + * Draws a previously-cached glyph at the given coordinates within the current + * drawing surface. + * + * @param context The rdpContext associated with the current RDP session. + * @param glyph The cached glyph to draw. + * @param x The destination X coordinate of the upper-left corner of the glyph. + * @param y The destination Y coordinate of the upper-left corner of the glyph. + */ void guac_rdp_glyph_draw(rdpContext* context, rdpGlyph* glyph, int x, int y); + +/** + * Frees any Guacamole-specific data associated with the given glyph, such that + * it can be safely freed by FreeRDP. + * + * @param context The rdpContext associated with the current RDP session. + * @param glyph The cached glyph to free. + */ void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph); + +/** + * Called just prior to rendering a series of glyphs. After this function is + * called, the glyphs will be individually rendered by calls to + * guac_rdp_glyph_draw(). + * + * @param context The rdpContext associated with the current RDP session. + * + * @param x + * The X coordinate of the upper-left corner of the background rectangle of + * the drawing operation, or 0 if the background is transparent. + * + * @param y + * The Y coordinate of the upper-left corner of the background rectangle of + * the drawing operation, or 0 if the background is transparent. + * + * @param width + * The width of the background rectangle of the drawing operation, or 0 if + * the background is transparent. + * + * @param height + * The height of the background rectangle of the drawing operation, or 0 if + * the background is transparent. + * + * @param fgcolor + * The foreground color of each glyph. This color will be in the colorspace + * of the RDP session, and may even be a palette index, and must be + * translated via guac_rdp_convert_color(). + * + * @param bgcolor + * The background color of the drawing area. This color will be in the + * colorspace of the RDP session, and may even be a palette index, and must + * be translated via guac_rdp_convert_color(). If the background is + * transparent, this value is undefined. + */ void guac_rdp_glyph_begindraw(rdpContext* context, int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); + +/** + * Called immediately after rendering a series of glyphs. Unlike + * guac_rdp_glyph_begindraw(), there is no way to detect through any invocation + * of this function whether the background color is opaque or transparent. We + * currently do NOT implement this function. + * + * @param context The rdpContext associated with the current RDP session. + * + * @param x + * The X coordinate of the upper-left corner of the background rectangle of + * the drawing operation. + * + * @param y + * The Y coordinate of the upper-left corner of the background rectangle of + * the drawing operation. + * + * @param width + * The width of the background rectangle of the drawing operation. + * + * @param height + * The height of the background rectangle of the drawing operation. + * + * @param fgcolor + * The foreground color of each glyph. This color will be in the colorspace + * of the RDP session, and may even be a palette index, and must be + * translated via guac_rdp_convert_color(). + * + * @param bgcolor + * The background color of the drawing area. This color will be in the + * colorspace of the RDP session, and may even be a palette index, and must + * be translated via guac_rdp_convert_color(). If the background is + * transparent, this value is undefined. + */ void guac_rdp_glyph_enddraw(rdpContext* context, int x, int y, int width, int height, UINT32 fgcolor, UINT32 bgcolor); diff --git a/src/protocols/rdp/rdp_keymap.h b/src/protocols/rdp/rdp_keymap.h index 52840e11..c4e76554 100644 --- a/src/protocols/rdp/rdp_keymap.h +++ b/src/protocols/rdp/rdp_keymap.h @@ -112,6 +112,9 @@ typedef int guac_rdp_keysym_state_map[0x200][0x100]; /** * Simple macro for determing whether a keysym can be stored (or retrieved) * from any keymap. + * + * @param keysym The keysym to check. + * @return Non-zero if the keysym can be stored or retrieved, zero otherwise. */ #define GUAC_RDP_KEYSYM_STORABLE(keysym) ((keysym) <= 0xFFFF || ((keysym) & 0xFFFF0000) == 0x01000000) @@ -120,6 +123,12 @@ typedef int guac_rdp_keysym_state_map[0x200][0x100]; * keysym. The idea here is that a keysym of the form 0xABCD will map to * mapping[0xAB][0xCD] while a keysym of the form 0x100ABCD will map to * mapping[0x1AB][0xCD]. + * + * @param keysym_mapping + * A 512-entry array of 256-entry arrays of arbitrary values, where the + * location of each array and value is determined by the given keysym. + * + * @param keysym The keysym of the entry to look up. */ #define GUAC_RDP_KEYSYM_LOOKUP(keysym_mapping, keysym) ( \ (keysym_mapping) \ @@ -196,6 +205,9 @@ extern const guac_rdp_keymap* GUAC_KEYMAPS[]; /** * Return the keymap having the given name, if any, or NULL otherwise. + * + * @param name The name of the keymap to find. + * @return The keymap having the given name, or NULL if no such keymap exists. */ const guac_rdp_keymap* guac_rdp_keymap_find(const char* name); diff --git a/src/protocols/rdp/rdp_pointer.c b/src/protocols/rdp/rdp_pointer.c index 1d214df2..7423e329 100644 --- a/src/protocols/rdp/rdp_pointer.c +++ b/src/protocols/rdp/rdp_pointer.c @@ -23,28 +23,30 @@ #include "config.h" #include "client.h" +#include "guac_cursor.h" +#include "guac_display.h" +#include "rdp.h" #include "rdp_pointer.h" #include #include #include -#include -#include #include void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_socket* socket = client->socket; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Allocate buffer */ + guac_common_display_layer* buffer = guac_common_display_alloc_buffer( + rdp_client->display, pointer->width, pointer->height); /* Allocate data for image */ unsigned char* data = (unsigned char*) malloc(pointer->width * pointer->height * 4); - /* Allocate layer */ - guac_layer* buffer = guac_client_alloc_buffer(client); - cairo_surface_t* surface; /* Convert to alpha cursor if mask data present */ @@ -60,8 +62,7 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { pointer->width, pointer->height, 4*pointer->width); /* Send surface to buffer */ - guac_client_stream_png(client, socket, GUAC_COMP_SRC, buffer, - 0, 0, surface); + guac_common_surface_draw(buffer->surface, 0, 0, surface); /* Free surface */ cairo_surface_destroy(surface); @@ -75,19 +76,23 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_socket* socket = client->socket; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Set cursor */ - guac_protocol_send_cursor(socket, pointer->xPos, pointer->yPos, - ((guac_rdp_pointer*) pointer)->layer, - 0, 0, pointer->width, pointer->height); + guac_common_cursor_set_surface(rdp_client->display->cursor, + pointer->xPos, pointer->yPos, + ((guac_rdp_pointer*) pointer)->layer->surface); } void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer) { guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_client_free_buffer(client, ((guac_rdp_pointer*) pointer)->layer); + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_common_display_layer* buffer = ((guac_rdp_pointer*) pointer)->layer; + + /* Free buffer */ + guac_common_display_free_buffer(rdp_client->display, buffer); } diff --git a/src/protocols/rdp/rdp_pointer.h b/src/protocols/rdp/rdp_pointer.h index f488e486..5d9ccd16 100644 --- a/src/protocols/rdp/rdp_pointer.h +++ b/src/protocols/rdp/rdp_pointer.h @@ -25,10 +25,13 @@ #define _GUAC_RDP_RDP_POINTER_H #include "config.h" +#include "guac_display.h" #include -#include +/** + * Guacamole-specific rdpPointer data. + */ typedef struct guac_rdp_pointer { /** @@ -37,16 +40,52 @@ typedef struct guac_rdp_pointer { rdpPointer pointer; /** - * Guacamole layer containing cached image data. + * The display layer containing cached image data. */ - guac_layer* layer; + guac_common_display_layer* layer; } guac_rdp_pointer; +/** + * Caches a new pointer, which can later be set via guac_rdp_pointer_set() as + * the current mouse pointer. + * + * @param context The rdpContext associated with the current RDP session. + * @param pointer The pointer to cache. + */ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer); + +/** + * Sets the given cached pointer as the current pointer. The given pointer must + * have already been initialized through a call to guac_rdp_pointer_new(). + * + * @param context The rdpContext associated with the current RDP session. + * @param pointer The pointer to set as the current mouse pointer. + */ void guac_rdp_pointer_set(rdpContext* context, rdpPointer* pointer); + +/** + * Frees all Guacamole-related data associated with the given pointer, allowing + * FreeRDP to free the rest safely. + * + * @param context The rdpContext associated with the current RDP session. + * @param pointer The pointer to free. + */ void guac_rdp_pointer_free(rdpContext* context, rdpPointer* pointer); + +/** + * Hides the current mouse pointer. + * + * @param context The rdpContext associated with the current RDP session. + */ void guac_rdp_pointer_set_null(rdpContext* context); + +/** + * Sets the system-dependent (as in dependent on the client system) default + * pointer as the current pointer, rather than a cached pointer. + * + * @param context The rdpContext associated with the current RDP session. + */ void guac_rdp_pointer_set_default(rdpContext* context); #endif diff --git a/src/protocols/rdp/rdp_rail.c b/src/protocols/rdp/rdp_rail.c index d2b108bb..a001142c 100644 --- a/src/protocols/rdp/rdp_rail.c +++ b/src/protocols/rdp/rdp_rail.c @@ -23,6 +23,7 @@ #include "config.h" #include "client.h" +#include "rdp.h" #include "rdp_rail.h" #include "rdp_settings.h" @@ -88,8 +89,8 @@ void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event) { RAIL_SYSPARAM_ORDER* sysparam; /* Get channels */ - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; - rdpChannels* channels = client_data->rdp_inst->context->channels; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + rdpChannels* channels = rdp_client->rdp_inst->context->channels; /* Get sysparam structure */ #ifdef LEGACY_EVENT @@ -106,8 +107,8 @@ void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event) { /* Work area */ sysparam->workArea.left = 0; sysparam->workArea.top = 0; - sysparam->workArea.right = client_data->settings.width; - sysparam->workArea.bottom = client_data->settings.height; + sysparam->workArea.right = rdp_client->settings->width; + sysparam->workArea.bottom = rdp_client->settings->height; /* Taskbar */ sysparam->taskbarPos.left = 0; diff --git a/src/protocols/rdp/rdp_rail.h b/src/protocols/rdp/rdp_rail.h index 9eadf470..10ec5e76 100644 --- a/src/protocols/rdp/rdp_rail.h +++ b/src/protocols/rdp/rdp_rail.h @@ -36,11 +36,18 @@ /** * Dispatches a given RAIL event to the appropriate handler. + * + * @param client The guac_client associated with the current RDP session. + * @param event The RAIL event to process. */ void guac_rdp_process_rail_event(guac_client* client, wMessage* event); /** - * Handles the event sent when updating system parameters. + * Handles the event sent when updating system parameters. The event given + * MUST be a SYSPARAM event. + * + * @param client The guac_client associated with the current RDP session. + * @param event The system parameter event to process. */ void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event); diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index ef12e15e..ae159e11 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -22,10 +22,15 @@ #include "config.h" +#include "client.h" +#include "guac_string.h" +#include "rdp.h" #include "rdp_settings.h" +#include "resolution.h" #include #include +#include #ifdef ENABLE_WINPR #include @@ -36,6 +41,465 @@ #include #include +/* Client plugin arguments */ +const char* GUAC_RDP_CLIENT_ARGS[] = { + "hostname", + "port", + "domain", + "username", + "password", + "width", + "height", + "dpi", + "initial-program", + "color-depth", + "disable-audio", + "enable-printing", + "enable-drive", + "drive-path", + "create-drive-path", + "console", + "console-audio", + "server-layout", + "security", + "ignore-cert", + "disable-auth", + "remote-app", + "remote-app-dir", + "remote-app-args", + "static-channels", + "client-name", + "enable-wallpaper", + "enable-theming", + "enable-font-smoothing", + "enable-full-window-drag", + "enable-desktop-composition", + "enable-menu-animations", + "preconnection-id", + "preconnection-blob", + +#ifdef ENABLE_COMMON_SSH + "enable-sftp", + "sftp-hostname", + "sftp-port", + "sftp-username", + "sftp-password", + "sftp-private-key", + "sftp-passphrase", + "sftp-directory", +#endif + + NULL +}; + +enum RDP_ARGS_IDX { + + IDX_HOSTNAME, + IDX_PORT, + IDX_DOMAIN, + IDX_USERNAME, + IDX_PASSWORD, + IDX_WIDTH, + IDX_HEIGHT, + IDX_DPI, + IDX_INITIAL_PROGRAM, + IDX_COLOR_DEPTH, + IDX_DISABLE_AUDIO, + IDX_ENABLE_PRINTING, + IDX_ENABLE_DRIVE, + IDX_DRIVE_PATH, + IDX_CREATE_DRIVE_PATH, + IDX_CONSOLE, + IDX_CONSOLE_AUDIO, + IDX_SERVER_LAYOUT, + IDX_SECURITY, + IDX_IGNORE_CERT, + IDX_DISABLE_AUTH, + IDX_REMOTE_APP, + IDX_REMOTE_APP_DIR, + IDX_REMOTE_APP_ARGS, + IDX_STATIC_CHANNELS, + IDX_CLIENT_NAME, + IDX_ENABLE_WALLPAPER, + IDX_ENABLE_THEMING, + IDX_ENABLE_FONT_SMOOTHING, + IDX_ENABLE_FULL_WINDOW_DRAG, + IDX_ENABLE_DESKTOP_COMPOSITION, + IDX_ENABLE_MENU_ANIMATIONS, + IDX_PRECONNECTION_ID, + IDX_PRECONNECTION_BLOB, + +#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 + + RDP_ARGS_COUNT +}; + +guac_rdp_settings* guac_rdp_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != RDP_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + RDP_ARGS_COUNT, argc); + return NULL; + } + + guac_rdp_settings* settings = calloc(1, sizeof(guac_rdp_settings)); + + /* Use console */ + settings->console = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_CONSOLE, 0); + + /* Enable/disable console audio */ + settings->console_audio = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_CONSOLE_AUDIO, 0); + + /* Ignore SSL/TLS certificate */ + settings->ignore_certificate = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_IGNORE_CERT, 0); + + /* Disable authentication */ + settings->disable_authentication = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DISABLE_AUTH, 0); + + /* NLA security */ + if (strcmp(argv[IDX_SECURITY], "nla") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Security mode: NLA"); + settings->security_mode = GUAC_SECURITY_NLA; + } + + /* TLS security */ + else if (strcmp(argv[IDX_SECURITY], "tls") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Security mode: TLS"); + settings->security_mode = GUAC_SECURITY_TLS; + } + + /* RDP security */ + else if (strcmp(argv[IDX_SECURITY], "rdp") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Security mode: RDP"); + settings->security_mode = GUAC_SECURITY_RDP; + } + + /* ANY security (allow server to choose) */ + else if (strcmp(argv[IDX_SECURITY], "any") == 0) { + guac_user_log(user, GUAC_LOG_INFO, "Security mode: ANY"); + settings->security_mode = GUAC_SECURITY_ANY; + } + + /* If nothing given, default to RDP */ + else { + guac_user_log(user, GUAC_LOG_INFO, "No security mode specified. Defaulting to RDP."); + settings->security_mode = GUAC_SECURITY_RDP; + } + + /* Set hostname */ + settings->hostname = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_HOSTNAME, ""); + + /* If port specified, use it */ + settings->port = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_PORT, RDP_DEFAULT_PORT); + + guac_user_log(user, GUAC_LOG_DEBUG, + "User resolution is %ix%i at %i DPI", + user->info.optimal_width, + user->info.optimal_height, + user->info.optimal_resolution); + + /* Use suggested resolution unless overridden */ + settings->resolution = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DPI, guac_rdp_suggest_resolution(user)); + + /* Use optimal width unless overridden */ + settings->width = user->info.optimal_width + * settings->resolution + / user->info.optimal_resolution; + + if (argv[IDX_WIDTH][0] != '\0') + settings->width = atoi(argv[IDX_WIDTH]); + + /* Use default width if given width is invalid. */ + if (settings->width <= 0) { + settings->width = RDP_DEFAULT_WIDTH; + guac_user_log(user, GUAC_LOG_ERROR, + "Invalid width: \"%s\". Using default of %i.", + argv[IDX_WIDTH], settings->width); + } + + /* Round width down to nearest multiple of 4 */ + settings->width = settings->width & ~0x3; + + /* Use optimal height unless overridden */ + settings->height = user->info.optimal_height + * settings->resolution + / user->info.optimal_resolution; + + if (argv[IDX_HEIGHT][0] != '\0') + settings->height = atoi(argv[IDX_HEIGHT]); + + /* Use default height if given height is invalid. */ + if (settings->height <= 0) { + settings->height = RDP_DEFAULT_HEIGHT; + guac_user_log(user, GUAC_LOG_ERROR, + "Invalid height: \"%s\". Using default of %i.", + argv[IDX_WIDTH], settings->height); + } + + guac_user_log(user, GUAC_LOG_DEBUG, + "Using resolution of %ix%i at %i DPI", + settings->width, + settings->height, + settings->resolution); + + /* Domain */ + settings->domain = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DOMAIN, NULL); + + /* Username */ + settings->username = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_USERNAME, NULL); + + /* Password */ + settings->password = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_PASSWORD, NULL); + + /* Client name */ + settings->client_name = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_CLIENT_NAME, NULL); + + /* Initial program */ + settings->initial_program = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_INITIAL_PROGRAM, NULL); + + /* RemoteApp program */ + settings->remote_app = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_REMOTE_APP, NULL); + + /* RemoteApp working directory */ + settings->remote_app_dir = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_REMOTE_APP_DIR, NULL); + + /* RemoteApp arguments */ + settings->remote_app_args = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_REMOTE_APP_ARGS, NULL); + + /* Static virtual channels */ + settings->svc_names = NULL; + if (argv[IDX_STATIC_CHANNELS][0] != '\0') + settings->svc_names = guac_split(argv[IDX_STATIC_CHANNELS], ','); + + /* + * Performance flags + */ + + settings->wallpaper_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_WALLPAPER, 0); + + settings->theming_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_THEMING, 0); + + settings->font_smoothing_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_FONT_SMOOTHING, 0); + + settings->full_window_drag_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_FULL_WINDOW_DRAG, 0); + + settings->desktop_composition_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_DESKTOP_COMPOSITION, 0); + + settings->menu_animations_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_MENU_ANIMATIONS, 0); + + /* Session color depth */ + settings->color_depth = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_COLOR_DEPTH, RDP_DEFAULT_DEPTH); + + /* Preconnection ID */ + settings->preconnection_id = -1; + if (argv[IDX_PRECONNECTION_ID][0] != '\0') { + + /* Parse preconnection ID, warn if invalid */ + int preconnection_id = atoi(argv[IDX_PRECONNECTION_ID]); + if (preconnection_id < 0) + guac_user_log(user, GUAC_LOG_WARNING, + "Ignoring invalid preconnection ID: %i", + preconnection_id); + + /* Otherwise, assign specified ID */ + else { + settings->preconnection_id = preconnection_id; + guac_user_log(user, GUAC_LOG_DEBUG, + "Preconnection ID: %i", settings->preconnection_id); + } + + } + + /* Preconnection BLOB */ + settings->preconnection_blob = NULL; + if (argv[IDX_PRECONNECTION_BLOB][0] != '\0') { + settings->preconnection_blob = strdup(argv[IDX_PRECONNECTION_BLOB]); + guac_user_log(user, GUAC_LOG_DEBUG, + "Preconnection BLOB: \"%s\"", settings->preconnection_blob); + } + +#ifndef HAVE_RDPSETTINGS_SENDPRECONNECTIONPDU + /* Warn if support for the preconnection BLOB / ID is absent */ + if (settings->preconnection_blob != NULL + || settings->preconnection_id != -1) { + guac_user_log(user, GUAC_LOG_WARNING, + "Installed version of FreeRDP lacks support for the " + "preconnection PDU. The specified preconnection BLOB and/or " + "ID will be ignored."); + } +#endif + + /* Audio enable/disable */ + settings->audio_enabled = + !guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DISABLE_AUDIO, 0); + + /* Printing enable/disable */ + settings->printing_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_PRINTING, 0); + + /* Drive enable/disable */ + settings->drive_enabled = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_DRIVE, 0); + + settings->drive_path = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DRIVE_PATH, ""); + + settings->create_drive_path = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_CREATE_DRIVE_PATH, 0); + + /* Pick keymap based on argument */ + settings->server_layout = NULL; + if (argv[IDX_SERVER_LAYOUT][0] != '\0') + settings->server_layout = + guac_rdp_keymap_find(argv[IDX_SERVER_LAYOUT]); + + /* If no keymap requested, use default */ + if (settings->server_layout == NULL) + settings->server_layout = guac_rdp_keymap_find(GUAC_DEFAULT_KEYMAP); + +#ifdef ENABLE_COMMON_SSH + /* SFTP enable/disable */ + settings->enable_sftp = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_ENABLE_SFTP, 0); + + /* Hostname for SFTP connection */ + settings->sftp_hostname = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_HOSTNAME, settings->hostname); + + /* Port for SFTP connection */ + settings->sftp_port = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_PORT, "22"); + + /* Username for SSH/SFTP authentication */ + settings->sftp_username = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_USERNAME, + settings->username != NULL ? settings->username : ""); + + /* Password for SFTP (if not using private key) */ + settings->sftp_password = + guac_user_parse_args_string(user, GUAC_RDP_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_RDP_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_RDP_CLIENT_ARGS, argv, + IDX_SFTP_PASSPHRASE, ""); + + /* Default upload directory */ + settings->sftp_directory = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SFTP_DIRECTORY, NULL); +#endif + + /* Success */ + return settings; + +} + +void guac_rdp_settings_free(guac_rdp_settings* settings) { + + /* Free settings strings */ + free(settings->client_name); + free(settings->domain); + free(settings->drive_path); + free(settings->hostname); + free(settings->initial_program); + free(settings->password); + free(settings->preconnection_blob); + free(settings->remote_app); + free(settings->remote_app_args); + free(settings->remote_app_dir); + free(settings->username); + + /* Free channel name array */ + free(settings->svc_names); + +#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 + + /* Free settings structure */ + free(settings); + +} + int guac_rdp_get_width(freerdp* rdp) { #ifdef LEGACY_RDPSETTINGS return rdp->settings->width; diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index aa60b568..b80b18fe 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -280,25 +280,121 @@ typedef struct guac_rdp_settings { */ char* preconnection_blob; +#ifdef ENABLE_COMMON_SSH + /** + * Whether SFTP should be enabled for the VNC connection. + */ + int enable_sftp; + + /** + * The hostname of the SSH server to connect to for SFTP. + */ + char* sftp_hostname; + + /** + * The port of the SSH server to connect to for SFTP. + */ + char* sftp_port; + + /** + * The username to provide when authenticating with the SSH server for + * SFTP. + */ + char* sftp_username; + + /** + * The password to provide when authenticating with the SSH server for + * SFTP (if not using a private key). + */ + char* sftp_password; + + /** + * The base64-encoded private key to use when authenticating with the SSH + * server for SFTP (if not using a password). + */ + char* sftp_private_key; + + /** + * The passphrase to use to decrypt the provided base64-encoded private + * key. + */ + char* sftp_passphrase; + + /** + * The default location for file uploads within the SSH server. This will + * apply only to uploads which do not use the filesystem guac_object (where + * the destination directory is otherwise ambiguous). + */ + char* sftp_directory; +#endif + } guac_rdp_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_rdp_settings_free() when no longer needed. If the arguments fail + * to parse, NULL is returned. + */ +guac_rdp_settings* guac_rdp_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the given guac_rdp_settings object, having been previously allocated + * via guac_rdp_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_rdp_settings_free(guac_rdp_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_RDP_CLIENT_ARGS[]; + /** * Save all given settings to the given freerdp instance. + * + * @param guac_settings The guac_rdp_settings object to save. + * @param rdp The RDP instance to save settings to. */ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp); /** * Returns the width of the RDP session display. + * + * @param rdp The RDP instance to retrieve the width from. + * @return The current width of the RDP display, in pixels. */ int guac_rdp_get_width(freerdp* rdp); /** * Returns the height of the RDP session display. + * + * @param rdp The RDP instance to retrieve the height from. + * @return The current height of the RDP display, in pixels. */ int guac_rdp_get_height(freerdp* rdp); /** * Returns the depth of the RDP session display. + * + * @param rdp The RDP instance to retrieve the depth from. + * @return The current depth of the RDP display, in bits per pixel. */ int guac_rdp_get_depth(freerdp* rdp); diff --git a/src/protocols/rdp/rdp_stream.c b/src/protocols/rdp/rdp_stream.c index 16aa560e..f829fbe7 100644 --- a/src/protocols/rdp/rdp_stream.c +++ b/src/protocols/rdp/rdp_stream.c @@ -24,6 +24,7 @@ #include "config.h" #include "client.h" #include "guac_clipboard.h" +#include "rdp.h" #include "rdp_fs.h" #include "rdp_svc.h" #include "rdp_stream.h" @@ -82,19 +83,22 @@ static void __generate_upload_path(const char* filename, char* path) { } -int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream, +int guac_rdp_upload_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename) { + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + int file_id; guac_rdp_stream* rdp_stream; char file_path[GUAC_RDP_FS_MAX_PATH]; /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + guac_rdp_fs* fs = rdp_client->filesystem; if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -105,9 +109,9 @@ int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream, file_id = guac_rdp_fs_open(fs, file_path, ACCESS_GENERIC_WRITE, 0, DISP_FILE_OVERWRITE_IF, 0); if (file_id < 0) { - guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)", + guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)", GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -120,31 +124,31 @@ int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream, stream->blob_handler = guac_rdp_upload_blob_handler; stream->end_handler = guac_rdp_upload_end_handler; - guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)", + guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } -int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream, +int guac_rdp_svc_pipe_handler(guac_user* user, guac_stream* stream, char* mimetype, char* name) { guac_rdp_stream* rdp_stream; - guac_rdp_svc* svc = guac_rdp_get_svc(client, name); + guac_rdp_svc* svc = guac_rdp_get_svc(user->client, name); /* Fail if no such SVC */ if (svc == NULL) { - guac_client_log(client, GUAC_LOG_ERROR, + guac_user_log(user, GUAC_LOG_ERROR, "Requested non-existent pipe: \"%s\".", name); - guac_protocol_send_ack(client->socket, stream, "FAIL (NO SUCH PIPE)", + guac_protocol_send_ack(user->socket, stream, "FAIL (NO SUCH PIPE)", GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } else - guac_client_log(client, GUAC_LOG_ERROR, + guac_user_log(user, GUAC_LOG_ERROR, "Inbound half of channel \"%s\" connected.", name); @@ -159,10 +163,11 @@ int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream, } -int guac_rdp_clipboard_handler(guac_client* client, guac_stream* stream, +int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream, char* mimetype) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_stream* rdp_stream; /* Init stream data */ @@ -171,23 +176,25 @@ int guac_rdp_clipboard_handler(guac_client* client, guac_stream* stream, stream->end_handler = guac_rdp_clipboard_end_handler; rdp_stream->type = GUAC_RDP_INBOUND_CLIPBOARD_STREAM; - guac_common_clipboard_reset(client_data->clipboard, mimetype); + guac_common_clipboard_reset(rdp_client->clipboard, mimetype); return 0; } -int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream, +int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { int bytes_written; guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; /* Get filesystem, return error if no filesystem 0*/ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_fs* fs = rdp_client->filesystem; if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -202,10 +209,10 @@ int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream, /* On error, abort */ if (bytes_written < 0) { - guac_protocol_send_ack(client->socket, stream, + guac_protocol_send_ack(user->socket, stream, "FAIL (BAD WRITE)", GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -216,14 +223,14 @@ int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream, } - guac_protocol_send_ack(client->socket, stream, "OK (DATA RECEIVED)", + guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } -int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream, +int guac_rdp_svc_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; @@ -231,32 +238,35 @@ int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream, /* Write blob data to SVC directly */ guac_rdp_svc_write(rdp_stream->svc, data, length); - guac_protocol_send_ack(client->socket, stream, "OK (DATA RECEIVED)", + guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } -int guac_rdp_clipboard_blob_handler(guac_client* client, guac_stream* stream, +int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; - guac_common_clipboard_append(client_data->clipboard, (char*) data, length); + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_common_clipboard_append(rdp_client->clipboard, (char*) data, length); return 0; } -int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream) { +int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) { + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + guac_rdp_fs* fs = rdp_client->filesystem; if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -264,19 +274,20 @@ int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream) { guac_rdp_fs_close(fs, rdp_stream->upload_status.file_id); /* Acknowledge stream end */ - guac_protocol_send_ack(client->socket, stream, "OK (STREAM END)", + guac_protocol_send_ack(user->socket, stream, "OK (STREAM END)", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); free(rdp_stream); return 0; } -int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream) { +int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; - rdpChannels* channels = client_data->rdp_inst->context->channels; + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + rdpChannels* channels = rdp_client->rdp_inst->context->channels; RDP_CB_FORMAT_LIST_EVENT* format_list = (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new( @@ -285,7 +296,7 @@ int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream) { NULL, NULL); /* Terminate clipboard data with NULL */ - guac_common_clipboard_append(client_data->clipboard, "", 1); + guac_common_clipboard_append(rdp_client->clipboard, "", 1); /* Notify server that text data is now available */ format_list->formats = (UINT32*) malloc(sizeof(UINT32)); @@ -298,17 +309,19 @@ int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream) { return 0; } -int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, +int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status) { + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + guac_rdp_fs* fs = rdp_client->filesystem; if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -324,39 +337,39 @@ int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, /* If bytes read, send as blob */ if (bytes_read > 0) { rdp_stream->download_status.offset += bytes_read; - guac_protocol_send_blob(client->socket, stream, + guac_protocol_send_blob(user->socket, stream, buffer, bytes_read); } /* If EOF, send end */ else if (bytes_read == 0) { - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); + guac_protocol_send_end(user->socket, stream); + guac_user_free_stream(user, stream); free(rdp_stream); } /* Otherwise, fail stream */ else { - guac_client_log(client, GUAC_LOG_ERROR, + guac_user_log(user, GUAC_LOG_ERROR, "Error reading file for download"); - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); + guac_protocol_send_end(user->socket, stream); + guac_user_free_stream(user, stream); free(rdp_stream); } - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); } - /* Otherwise, return stream to client */ + /* Otherwise, return stream to user */ else - guac_client_free_stream(client, stream); + guac_user_free_stream(user, stream); return 0; } -int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, +int guac_rdp_ls_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status) { int blob_written = 0; @@ -368,7 +381,7 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { guac_rdp_fs_close(rdp_stream->ls_status.fs, rdp_stream->ls_status.file_id); - guac_client_free_stream(client, stream); + guac_user_free_stream(user, stream); free(rdp_stream); return 0; } @@ -388,7 +401,7 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, if (!guac_rdp_fs_append_filename(absolute_path, rdp_stream->ls_status.directory_name, filename)) { - guac_client_log(client, GUAC_LOG_DEBUG, + guac_user_log(user, GUAC_LOG_DEBUG, "Skipping filename \"%s\" - filename is invalid or " "resulting path is too long", filename); @@ -414,12 +427,12 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, /* Determine mimetype */ const char* mimetype; if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) - mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE; + mimetype = GUAC_USER_STREAM_INDEX_MIMETYPE; else mimetype = "application/octet-stream"; /* Write entry */ - blob_written |= guac_common_json_write_property(client, stream, + blob_written |= guac_common_json_write_property(user, stream, &rdp_stream->ls_status.json_state, absolute_path, mimetype); guac_rdp_fs_close(rdp_stream->ls_status.fs, file_id); @@ -430,9 +443,9 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, if (filename == NULL) { /* Complete JSON object */ - guac_common_json_end_object(client, stream, + guac_common_json_end_object(user, stream, &rdp_stream->ls_status.json_state); - guac_common_json_flush(client, stream, + guac_common_json_flush(user, stream, &rdp_stream->ls_status.json_state); /* Clean up resources */ @@ -441,21 +454,24 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, free(rdp_stream); /* Signal of stream */ - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); + guac_protocol_send_end(user->socket, stream); + guac_user_free_stream(user, stream); } - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } -int guac_rdp_download_get_handler(guac_client* client, guac_object* object, +int guac_rdp_download_get_handler(guac_user* user, guac_object* object, char* name) { + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + /* Get filesystem, ignore request if no filesystem */ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + guac_rdp_fs* fs = rdp_client->filesystem; if (fs == NULL) return 0; @@ -463,7 +479,7 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object, int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_READ, 0, DISP_FILE_OPEN, 0); if (file_id < 0) { - guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"", + guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"", name); return 0; } @@ -489,17 +505,17 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object, sizeof(rdp_stream->ls_status.directory_name) - 1); /* Allocate stream for body */ - guac_stream* stream = guac_client_alloc_stream(client); + guac_stream* stream = guac_user_alloc_stream(user); stream->ack_handler = guac_rdp_ls_ack_handler; stream->data = rdp_stream; /* Init JSON object state */ - guac_common_json_begin_object(client, stream, + guac_common_json_begin_object(user, stream, &rdp_stream->ls_status.json_state); /* Associate new stream with get request */ - guac_protocol_send_body(client->socket, object, stream, - GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name); + guac_protocol_send_body(user->socket, object, stream, + GUAC_USER_STREAM_INDEX_MIMETYPE, name); } @@ -513,29 +529,32 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object, rdp_stream->download_status.offset = 0; /* Allocate stream for body */ - guac_stream* stream = guac_client_alloc_stream(client); + guac_stream* stream = guac_user_alloc_stream(user); stream->data = rdp_stream; stream->ack_handler = guac_rdp_download_ack_handler; /* Associate new stream with get request */ - guac_protocol_send_body(client->socket, object, stream, + guac_protocol_send_body(user->socket, object, stream, "application/octet-stream", name); } - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } -int guac_rdp_upload_put_handler(guac_client* client, guac_object* object, +int guac_rdp_upload_put_handler(guac_user* user, guac_object* object, guac_stream* stream, char* mimetype, char* name) { + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + guac_rdp_fs* fs = rdp_client->filesystem; if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)", GUAC_PROTOCOL_STATUS_SERVER_ERROR); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -545,9 +564,9 @@ int guac_rdp_upload_put_handler(guac_client* client, guac_object* object, /* Abort on failure */ if (file_id < 0) { - guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)", + guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)", GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } @@ -563,9 +582,9 @@ int guac_rdp_upload_put_handler(guac_client* client, guac_object* object, stream->end_handler = guac_rdp_upload_end_handler; /* Acknowledge stream creation */ - guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)", + guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); + guac_socket_flush(user->socket); return 0; } diff --git a/src/protocols/rdp/rdp_stream.h b/src/protocols/rdp/rdp_stream.h index fe0b0391..ced121c5 100644 --- a/src/protocols/rdp/rdp_stream.h +++ b/src/protocols/rdp/rdp_stream.h @@ -28,7 +28,7 @@ #include "guac_json.h" #include "rdp_svc.h" -#include +#include #include #include @@ -162,62 +162,113 @@ typedef struct guac_rdp_stream { /** * Handler for inbound files related to file uploads. + * + * @param user The user creating the file stream. + * @param stream The newly-created file stream. + * @param mimetype The mimetype of the file being received. + * @param filename The name of the file being received. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream, +int guac_rdp_upload_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename); /** * Handler for inbound pipes related to static virtual channels. + * + * @param user The user creating the pipe stream. + * @param stream The newly-created pipe stream. + * @param mimetype The mimetype of the pipe being received. + * @param name The name of the pipe being received. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream, +int guac_rdp_svc_pipe_handler(guac_user* user, guac_stream* stream, char* mimetype, char* name); /** * Handler for inbound clipboard data. + * + * @param user The user creating the clipboard stream. + * @param stream The newly-created clipboard stream. + * @param mimetype The mimetype of the clipboard data being received. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_clipboard_handler(guac_client* client, guac_stream* stream, +int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream, char* mimetype); /** * Handler for stream data related to file uploads. + * + * @param user The user sending data. + * @param stream The file stream receiving the given data. + * @param data The received data. + * @param length The number of bytes received. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream, +int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream, void* data, int length); /** * Handler for stream data related to static virtual channels. + * + * @param user The user sending data. + * @param stream The pipe stream receiving the given data. + * @param data The received data. + * @param length The number of bytes received. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream, +int guac_rdp_svc_blob_handler(guac_user* user, guac_stream* stream, void* data, int length); /** * Handler for stream data related to clipboard. + * + * @param user The user sending data. + * @param stream The clipboard stream receiving the given data. + * @param data The received data. + * @param length The number of bytes received. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_clipboard_blob_handler(guac_client* client, guac_stream* stream, +int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length); /** * Handler for end-of-stream related to file uploads. + * + * @param user The user closing the stream. + * @param stream The file stream being closed. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream); +int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream); /** * Handler for end-of-stream related to clipboard. + * + * @param user The user closing the stream. + * @param stream The clipboard stream being closed. + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream); +int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream); /** * Handler for acknowledgements of receipt of data related to file downloads. + * + * @param user The user acknowledging data receipt. + * @param stream The file stream associated with the data being acknowledged. + * @param message An arbitrary human-readable message acknowledging receipt. + * @param status + * The status code representing whether the data was received successfully. + * + * @return Zero if successful, non-zero if an error occurs. */ -int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, +int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status); /** * Handler for ack messages received due to receipt of a "body" or "blob" * instruction associated with a directory list operation. * - * @param client - * The client receiving the ack message. + * @param user + * The user receiving the ack message. * * @param stream * The Guacamole protocol stream associated with the received ack message. @@ -233,7 +284,7 @@ int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, * @return * Zero on success, non-zero on error. */ -int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, +int guac_rdp_ls_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status); /** @@ -241,8 +292,8 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, * via the Guacamole protocol, get messages request the body of a file within * the filesystem. * - * @param client - * The client receiving the get message. + * @param user + * The user receiving the get message. * * @param object * The Guacamole protocol object associated with the get request itself. @@ -253,7 +304,7 @@ int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream, * @return * Zero on success, non-zero on error. */ -int guac_rdp_download_get_handler(guac_client* client, guac_object* object, +int guac_rdp_download_get_handler(guac_user* user, guac_object* object, char* name); /** @@ -261,14 +312,14 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object, * via the Guacamole protocol, put messages request write access to a file * within the filesystem. * - * @param client - * The client receiving the put message. + * @param user + * The user receiving the put message. * * @param object * The Guacamole protocol object associated with the put request itself. * * @param stream - * The Guacamole protocol stream along which the client will be sending + * The Guacamole protocol stream along which the user will be sending * file data. * * @param mimetype @@ -280,7 +331,7 @@ int guac_rdp_download_get_handler(guac_client* client, guac_object* object, * @return * Zero on success, non-zero on error. */ -int guac_rdp_upload_put_handler(guac_client* client, guac_object* object, +int guac_rdp_upload_put_handler(guac_user* user, guac_object* object, guac_stream* stream, char* mimetype, char* name); #endif diff --git a/src/protocols/rdp/rdp_svc.c b/src/protocols/rdp/rdp_svc.c index 3a95406f..ff9d93cf 100644 --- a/src/protocols/rdp/rdp_svc.c +++ b/src/protocols/rdp/rdp_svc.c @@ -23,6 +23,7 @@ #include "config.h" #include "client.h" #include "guac_list.h" +#include "rdp.h" #include "rdp_svc.h" #include @@ -67,24 +68,24 @@ void guac_rdp_free_svc(guac_rdp_svc* svc) { void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Add to list of available SVC */ - guac_common_list_lock(client_data->available_svc); - guac_common_list_add(client_data->available_svc, svc); - guac_common_list_unlock(client_data->available_svc); + guac_common_list_lock(rdp_client->available_svc); + guac_common_list_add(rdp_client->available_svc, svc); + guac_common_list_unlock(rdp_client->available_svc); } guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_common_list_element* current; guac_rdp_svc* found = NULL; /* For each available SVC */ - guac_common_list_lock(client_data->available_svc); - current = client_data->available_svc->head; + guac_common_list_lock(rdp_client->available_svc); + current = rdp_client->available_svc->head; while (current != NULL) { /* If name matches, found */ @@ -97,7 +98,7 @@ guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) { current = current->next; } - guac_common_list_unlock(client_data->available_svc); + guac_common_list_unlock(rdp_client->available_svc); return found; @@ -105,19 +106,19 @@ guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) { guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_common_list_element* current; guac_rdp_svc* found = NULL; /* For each available SVC */ - guac_common_list_lock(client_data->available_svc); - current = client_data->available_svc->head; + guac_common_list_lock(rdp_client->available_svc); + current = rdp_client->available_svc->head; while (current != NULL) { /* If name matches, remove entry */ guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; if (strcmp(current_svc->name, name) == 0) { - guac_common_list_remove(client_data->available_svc, current); + guac_common_list_remove(rdp_client->available_svc, current); found = current_svc; break; } @@ -125,7 +126,7 @@ guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name) { current = current->next; } - guac_common_list_unlock(client_data->available_svc); + guac_common_list_unlock(rdp_client->available_svc); /* Return removed entry, if any */ return found; diff --git a/src/protocols/rdp/rdp_svc.h b/src/protocols/rdp/rdp_svc.h index 7b6e5910..87435219 100644 --- a/src/protocols/rdp/rdp_svc.h +++ b/src/protocols/rdp/rdp_svc.h @@ -71,31 +71,61 @@ typedef struct guac_rdp_svc { /** * Allocate a new SVC with the given name. + * + * @param client The guac_client associated with the current RDP session. + * @param name The name of the virtual channel to allocate. + * @return A newly-allocated static virtual channel. */ guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name); /** * Free the given SVC. + * + * @param svc The static virtual channel to free. */ void guac_rdp_free_svc(guac_rdp_svc* svc); /** * Add the given SVC to the list of all available SVCs. + * + * @param client The guac_client associated with the current RDP session. + * + * @param svc + * The static virtual channel to add to the list of all such channels + * available. */ void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc); /** * Retrieve the SVC with the given name from the list stored in the client. + * + * @param client The guac_client associated with the current RDP session. + * @param name The name of the static virtual channel to retrieve. + * + * @return + * The static virtual channel with the given name, or NULL if no such + * virtual channel exists. */ guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name); /** * Remove the SVC with the given name from the list stored in the client. + * + * @param client The guac_client associated with the current RDP session. + * @param name The name of the static virtual channel to remove. + * + * @return + * The static virtual channel that was removed, or NULL if no such virtual + * channel exists. */ guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name); /** * Write the given blob of data to the virtual channel. + * + * @param svc The static virtual channel to write data to. + * @param data The data to write. + * @param length The number of bytes to write. */ void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length); diff --git a/src/protocols/rdp/resolution.c b/src/protocols/rdp/resolution.c index 1d36a4ff..420d2dbf 100644 --- a/src/protocols/rdp/resolution.c +++ b/src/protocols/rdp/resolution.c @@ -23,38 +23,38 @@ #include "client.h" #include "resolution.h" -#include +#include -int guac_rdp_resolution_reasonable(guac_client* client, int resolution) { +int guac_rdp_resolution_reasonable(guac_user* user, int resolution) { - int width = client->info.optimal_width; - int height = client->info.optimal_height; + int width = user->info.optimal_width; + int height = user->info.optimal_height; - /* Convert client pixels to remote pixels */ - width = width * resolution / client->info.optimal_resolution; - height = height * resolution / client->info.optimal_resolution; + /* Convert user pixels to remote pixels */ + width = width * resolution / user->info.optimal_resolution; + height = height * resolution / user->info.optimal_resolution; /* - * Resolution is reasonable if the same as the client optimal resolution + * Resolution is reasonable if the same as the user optimal resolution * OR if the resulting display area is reasonable */ - return client->info.optimal_resolution == resolution + return user->info.optimal_resolution == resolution || width*height >= GUAC_RDP_REASONABLE_AREA; } -int guac_rdp_suggest_resolution(guac_client* client) { +int guac_rdp_suggest_resolution(guac_user* user) { /* Prefer RDP's native resolution */ - if (guac_rdp_resolution_reasonable(client, GUAC_RDP_NATIVE_RESOLUTION)) + if (guac_rdp_resolution_reasonable(user, GUAC_RDP_NATIVE_RESOLUTION)) return GUAC_RDP_NATIVE_RESOLUTION; /* If native resolution is too tiny, try higher resolution */ - if (guac_rdp_resolution_reasonable(client, GUAC_RDP_HIGH_RESOLUTION)) + if (guac_rdp_resolution_reasonable(user, GUAC_RDP_HIGH_RESOLUTION)) return GUAC_RDP_HIGH_RESOLUTION; - /* Fallback to client-suggested resolution */ - return client->info.optimal_resolution; + /* Fallback to user-suggested resolution */ + return user->info.optimal_resolution; } diff --git a/src/protocols/rdp/resolution.h b/src/protocols/rdp/resolution.h index 7f637d02..60f2d5e0 100644 --- a/src/protocols/rdp/resolution.h +++ b/src/protocols/rdp/resolution.h @@ -23,27 +23,27 @@ #ifndef GUAC_RDP_RESOLUTION_H #define GUAC_RDP_RESOLUTION_H -#include +#include /** - * Returns whether the given resolution is reasonable for the given client, + * Returns whether the given resolution is reasonable for the given user, * based on arbitrary criteria for reasonability. * - * @param client The guac_client to test the given resolution against. + * @param user The guac_user to test the given resolution against. * @param resolution The resolution to test, in DPI. * @return Non-zero if the resolution is reasonable, zero otherwise. */ -int guac_rdp_resolution_reasonable(guac_client* client, int resolution); +int guac_rdp_resolution_reasonable(guac_user* user, int resolution); /** * Returns a reasonable resolution for the remote display, given the size and - * resolution of a guac_client. + * resolution of a guac_user. * - * @param client The guac_client whose size and resolution shall be used to - * determine an appropriate remote display resolution. + * @param user The guac_user whose size and resolution shall be used to + * determine an appropriate remote display resolution. * @return A reasonable resolution for the remote display, in DPI. */ -int guac_rdp_suggest_resolution(guac_client* client); +int guac_rdp_suggest_resolution(guac_user* user); #endif diff --git a/src/protocols/rdp/sftp.c b/src/protocols/rdp/sftp.c index a394ee63..8294d027 100644 --- a/src/protocols/rdp/sftp.c +++ b/src/protocols/rdp/sftp.c @@ -22,21 +22,23 @@ #include "config.h" -#include "client.h" #include "guac_sftp.h" +#include "rdp.h" #include "sftp.h" #include #include +#include -int guac_rdp_sftp_file_handler(guac_client* client, guac_stream* stream, +int guac_rdp_sftp_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename) { - rdp_guac_client_data* client_data = (rdp_guac_client_data*) client->data; - guac_object* filesystem = client_data->sftp_filesystem; + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_common_ssh_sftp_filesystem* filesystem = rdp_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/rdp/sftp.h b/src/protocols/rdp/sftp.h index 8172ec5f..02639f8a 100644 --- a/src/protocols/rdp/sftp.h +++ b/src/protocols/rdp/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_rdp_sftp_file_handler(guac_client* client, guac_stream* stream, +int guac_rdp_sftp_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename); #endif diff --git a/src/protocols/rdp/unicode.h b/src/protocols/rdp/unicode.h index f6d24b93..4818c4c7 100644 --- a/src/protocols/rdp/unicode.h +++ b/src/protocols/rdp/unicode.h @@ -20,18 +20,30 @@ * THE SOFTWARE. */ - -#include "config.h" +#ifndef GUAC_RDP_UNICODE_H +#define GUAC_RDP_UNICODE_H /** * Convert the given number of UTF-16 characters to UTF-8 characters. + * + * @param utf16 Arbitrary UTF-16 data. + * @param length The length of the UTF-16 data, in characters. + * @param utf8 Buffer to which the converted UTF-8 data will be written. + * @param size The maximum number of bytes available in the UTF-8 buffer. */ void guac_rdp_utf16_to_utf8(const unsigned char* utf16, int length, char* utf8, int size); /** * Convert the given number of UTF-8 characters to UTF-16 characters. + * + * @param utf8 Arbitrary UTF-8 data. + * @param length The length of the UTF-8 data, in characters. + * @param utf16 Buffer to which the converted UTF-16 data will be written. + * @param size The maximum number of bytes available in the UTF-16 buffer. */ void guac_rdp_utf8_to_utf16(const unsigned char* utf8, int length, char* utf16, int size); +#endif + diff --git a/src/protocols/rdp/user.c b/src/protocols/rdp/user.c new file mode 100644 index 00000000..64fa5117 --- /dev/null +++ b/src/protocols/rdp/user.c @@ -0,0 +1,123 @@ +/* + * 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 "input.h" +#include "guac_display.h" +#include "user.h" +#include "rdp.h" +#include "rdp_settings.h" +#include "rdp_stream.h" + +#ifdef ENABLE_COMMON_SSH +#include "sftp.h" +#endif + +#include +#include +#include +#include +#include + +#include + +int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data; + + /* Connect via RDP if owner */ + if (user->owner) { + + /* Parse arguments into client */ + guac_rdp_settings* settings = rdp_client->settings = + guac_rdp_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(&rdp_client->client_thread, NULL, + guac_rdp_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 { + + /* Synchronize an audio stream */ + if (rdp_client->audio) + guac_audio_stream_add_user(rdp_client->audio, user); + + /* Synchronize with current display */ + guac_common_display_dup(rdp_client->display, user, user->socket); + guac_socket_flush(user->socket); + + } + + user->file_handler = guac_rdp_user_file_handler; + user->mouse_handler = guac_rdp_user_mouse_handler; + user->key_handler = guac_rdp_user_key_handler; + user->size_handler = guac_rdp_user_size_handler; + user->pipe_handler = guac_rdp_svc_pipe_handler; + user->clipboard_handler = guac_rdp_clipboard_handler; + + return 0; + +} + +int guac_rdp_user_file_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* filename) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data; + +#ifdef ENABLE_COMMON_SSH + guac_rdp_settings* settings = rdp_client->settings; + + /* If SFTP is enabled, it should be used for default uploads only if RDPDR + * is not enabled or its upload directory has been set */ + if (rdp_client->sftp_filesystem != NULL) { + if (!settings->drive_enabled || settings->sftp_directory != NULL) + return guac_rdp_sftp_file_handler(user, stream, mimetype, filename); + } +#endif + + /* Default to using RDPDR uploads (if enabled) */ + if (rdp_client->filesystem != NULL) + return guac_rdp_upload_file_handler(user, stream, mimetype, filename); + + /* File transfer not enabled */ + guac_protocol_send_ack(user->socket, stream, "File transfer disabled", + GUAC_PROTOCOL_STATUS_UNSUPPORTED); + guac_socket_flush(user->socket); + + return 0; +} + diff --git a/src/protocols/rdp/guac_handlers.h b/src/protocols/rdp/user.h similarity index 55% rename from src/protocols/rdp/guac_handlers.h rename to src/protocols/rdp/user.h index 29328349..eb00fd28 100644 --- a/src/protocols/rdp/guac_handlers.h +++ b/src/protocols/rdp/user.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * 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 @@ -20,19 +20,35 @@ * THE SOFTWARE. */ +#ifndef GUAC_RDP_USER_H +#define GUAC_RDP_USER_H -#ifndef _GUAC_RDP_GUAC_HANDLERS_H -#define _GUAC_RDP_GUAC_HANDLERS_H +#include -#include "config.h" +/** + * Handler for joining users. + * + * @param user The user joining the connection. + * + * @param argc + * The number of arguments provided by the user during the connection + * handshake. + * + * @param argv + * Array of values of each argument provided by the user during the + * connection handshake. + * + * @return Zero if the join operation succeeded, non-zero otherwise. + */ +int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv); -#include - -int rdp_guac_client_free_handler(guac_client* client); -int rdp_guac_client_handle_messages(guac_client* client); -int rdp_guac_client_mouse_handler(guac_client* client, int x, int y, int mask); -int rdp_guac_client_key_handler(guac_client* client, int keysym, int pressed); -int rdp_guac_client_size_handler(guac_client* client, int width, int height); +/** + * Handler for received simple file uploads. This handler will automatically + * select between RDPDR and SFTP depending on which is available and which has + * priority given associated settings. + */ +int guac_rdp_user_file_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* filename); #endif