diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index f76c33ef..3669b3dd 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -26,6 +26,7 @@ libguac_client_rdp_la_SOURCES = \ _generated_keymaps.c \ audio_input.c \ client.c \ + dvc.c \ input.c \ ptr_string.c \ rdp.c \ @@ -91,6 +92,7 @@ noinst_HEADERS = \ guac_svc/svc_service.h \ audio_input.h \ client.h \ + dvc.h \ input.h \ ptr_string.h \ rdp.h \ diff --git a/src/protocols/rdp/audio_input.c b/src/protocols/rdp/audio_input.c index 93c492b9..f2fd84a1 100644 --- a/src/protocols/rdp/audio_input.c +++ b/src/protocols/rdp/audio_input.c @@ -19,6 +19,7 @@ #include "config.h" #include "audio_input.h" +#include "dvc.h" #include "ptr_string.h" #include "rdp.h" @@ -71,17 +72,12 @@ int guac_rdp_audio_end_handler(guac_user* user, guac_stream* stream) { } -void guac_rdp_audio_load_plugin(rdpContext* context) { +void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) { guac_client* client = ((rdp_freerdp_context*) context)->client; /* Add "AUDIO_INPUT" channel */ - ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); - args->argc = 2; - args->argv = malloc(sizeof(char**) * 2); - args->argv[0] = strdup("guacai"); - args->argv[1] = guac_rdp_ptr_to_string(client); - freerdp_dynamic_channel_collection_add(context->settings, args); + guac_rdp_dvc_list_add(list, "guacai", guac_rdp_ptr_to_string(client), NULL); } diff --git a/src/protocols/rdp/audio_input.h b/src/protocols/rdp/audio_input.h index 776f0a52..e13b41e6 100644 --- a/src/protocols/rdp/audio_input.h +++ b/src/protocols/rdp/audio_input.h @@ -21,6 +21,7 @@ #define GUAC_RDP_AUDIO_INPUT_H #include "config.h" +#include "dvc.h" #include #include @@ -213,7 +214,7 @@ guac_user_end_handler guac_rdp_audio_end_handler; * @param context * The rdpContext associated with the active RDP session. */ -void guac_rdp_audio_load_plugin(rdpContext* context); +void guac_rdp_audio_load_plugin(rdpContext* context, guac_rdp_dvc_list* list); #endif diff --git a/src/protocols/rdp/dvc.c b/src/protocols/rdp/dvc.c new file mode 100644 index 00000000..0f0e835a --- /dev/null +++ b/src/protocols/rdp/dvc.c @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "dvc.h" +#include "guac_list.h" +#include "rdp.h" + +#include +#include +#include + +#include +#include + +guac_rdp_dvc_list* guac_rdp_dvc_list_alloc() { + + guac_rdp_dvc_list* list = malloc(sizeof(guac_rdp_dvc_list)); + + /* Initialize with empty backing list */ + list->channels = guac_common_list_alloc(); + list->channel_count = 0; + + return list; + +} + +void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...) { + + va_list args; + + guac_rdp_dvc* dvc = malloc(sizeof(guac_rdp_dvc)); + + va_start(args, name); + + /* Count number of arguments (excluding terminating NULL) */ + dvc->argc = 1; + while (va_arg(args, char*) != NULL) + dvc->argc++; + + /* Reset va_list */ + va_end(args); + va_start(args, name); + + /* Copy argument values into DVC entry */ + dvc->argv = malloc(sizeof(char*) * dvc->argc); + dvc->argv[0] = strdup(name); + int i; + for (i = 1; i < dvc->argc; i++) + dvc->argv[i] = strdup(va_arg(args, char*)); + + va_end(args); + + /* Add entry to DVC list */ + guac_common_list_add(list->channels, dvc); + + /* Update channel count */ + list->channel_count++; + +} + +void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list) { + + /* For each channel */ + guac_common_list_element* current = list->channels->head; + while (current != NULL) { + + /* Free arguments declaration for current channel */ + guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data; + + /* Free the underlying arguments list if not delegated to FreeRDP */ + if (dvc->argv != NULL) { + + /* Free each argument value */ + for (int i = 0; i < dvc->argc; i++) + free(dvc->argv[i]); + + free(dvc->argv); + } + + free(dvc); + + current = current->next; + + } + + /* Free underlying list */ + guac_common_list_free(list->channels); + + /* Free the DVC list itself */ + free(list); + +} + +int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + rdpChannels* channels = context->channels; + + /* Skip if no channels will be loaded */ + if (list->channel_count == 0) + return 0; + + /* For each channel */ + guac_common_list_element* current = list->channels->head; + while (current != NULL) { + + /* Get channel arguments */ + guac_rdp_dvc* dvc = (guac_rdp_dvc*) current->data; + current = current->next; + + /* guac_rdp_dvc_list_add() guarantees at one argument */ + assert(dvc->argc >= 1); + + /* guac_rdp_load_drdynvc() MUST only be invoked once */ + assert(dvc->argv != NULL); + + /* Log registration of plugin for current channel */ + guac_client_log(client, GUAC_LOG_DEBUG, + "Registering DVC plugin \"%s\"", dvc->argv[0]); + + /* Register plugin with FreeRDP */ + ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); + args->argc = dvc->argc; + args->argv = dvc->argv; + freerdp_dynamic_channel_collection_add(context->settings, args); + + /* Rely on FreeRDP to free argv storage */ + dvc->argv = NULL; + + } + + /* Load virtual channel management plugin */ + return freerdp_channels_load_plugin(channels, context->settings, + "drdynvc", context->settings); + +} + diff --git a/src/protocols/rdp/dvc.h b/src/protocols/rdp/dvc.h new file mode 100644 index 00000000..02ca6437 --- /dev/null +++ b/src/protocols/rdp/dvc.h @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_RDP_DVC_H +#define GUAC_RDP_DVC_H + +#include "config.h" +#include "guac_list.h" + +#include + +/** + * The set of all arguments that should be passed to a given dynamic virtual + * channel plugin, including the name of that plugin. + */ +typedef struct guac_rdp_dvc { + + /** + * The number of arguments in the argv array. This MUST be at least 1. + */ + int argc; + + /** + * The argument values being passed to the dynamic virtual channel plugin. + * The first entry in this array is always the name of the plugin. If + * guac_rdp_load_drdynvc() has been invoked, and freeing the argument + * values is being delegated to FreeRDP, this will be NULL. + */ + char** argv; + +} guac_rdp_dvc; + +/** + * A list of dynamic virtual channels which should be provided to the DRDYNVC + * plugin once loaded via guac_rdp_load_drdynvc(). This interface exists purely + * to bridge incompatibilities between differing versions of FreeRDP and its + * DRDYNVC plugin. Any allocated guac_rdp_dvc_list is unlikely to be needed + * after the DRDYNVC plugin has been loaded. + */ +typedef struct guac_rdp_dvc_list { + + /** + * Array of all dynamic virtual channels which should be registered with + * the DRDYNVC plugin once loaded. Each list element will point to a + * guac_rdp_dvc structure which must eventually be freed. + */ + guac_common_list* channels; + + /** + * The number of channels within the list. + */ + int channel_count; + +} guac_rdp_dvc_list; + +/** + * Allocates a new, empty list of dynamic virtual channels. New channels may + * be added via guac_rdp_dvc_list_add(). The loading of those channels' + * associated plugins will be deferred until guac_rdp_load_drdynvc() is + * invoked. + * + * @return + * A newly-allocated, empty list of dynamic virtual channels. + */ +guac_rdp_dvc_list* guac_rdp_dvc_list_alloc(); + +/** + * Adds the given dynamic virtual channel plugin name and associated arguments + * to the list. The provied arguments list is NOT optional and MUST be + * NULL-terminated, even if there are no arguments for the named dynamic + * virtual channel plugin. Though FreeRDP requires that the arguments for a + * dynamic virtual channel plugin contain the name of the plugin itself as the + * first argument, the name must be excluded from the arguments provided here. + * This function will automatically take care of adding the plugin name to + * the arguments. + * + * @param list + * The guac_rdp_dvc_list to which the given plugin name and arguments + * should be added, for later bulk registration via + * guac_rdp_load_drdynvc(). + * + * @param name + * The name of the dynamic virtual channel plugin that should be given + * the provided arguments when guac_rdp_load_drdynvc() is invoked. + * + * @param ... + * The string (char*) arguments which should be passed to the dynamic + * virtual channel plugin when it is loaded via guac_rdp_load_drdynvc(), + * excluding the plugin name itself. + */ +void guac_rdp_dvc_list_add(guac_rdp_dvc_list* list, const char* name, ...); + +/** + * Frees the given list of dynamic virtual channels. Note that, while each + * individual entry within this list will be freed, it is partially up to + * FreeRDP to free the storage associated with the arguments passed to the + * virtual channels. + * + * @param list + * The list to free. + */ +void guac_rdp_dvc_list_free(guac_rdp_dvc_list* list); + +/** + * Loads FreeRDP's DRDYNVC plugin and registers the dynamic virtual channel + * plugins described by the given guac_rdp_dvc_list. This function MUST be + * invoked no more than once per RDP connection. Invoking this function + * multiple times, even if the guac_rdp_dvc_list is different each time, will + * result in undefined behavior. + * + * @param context + * The rdpContext associated with the RDP connection for which the DRDYNVC + * plugin should be loaded. + * + * @param list + * A guac_rdp_dvc_list describing the dynamic virtual channel plugins that + * should be registered with the DRDYNVC plugin, along with any arguments. + */ +int guac_rdp_load_drdynvc(rdpContext* context, guac_rdp_dvc_list* list); + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index f4613692..d515fb11 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -21,6 +21,7 @@ #include "audio_input.h" #include "client.h" +#include "dvc.h" #include "guac_cursor.h" #include "guac_display.h" #include "guac_recording.h" @@ -212,6 +213,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { rdpPrimaryUpdate* primary; CLRCONV* clrconv; + guac_rdp_dvc_list* dvc_list = guac_rdp_dvc_list_alloc(); #ifdef HAVE_FREERDP_REGISTER_ADDIN_PROVIDER /* Init FreeRDP add-in provider */ @@ -224,34 +226,17 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { (pChannelConnectedEventHandler) guac_rdp_channel_connected); #endif - /* Load DRDYNVC plugin if required */ - if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE - || settings->enable_audio_input) { - - /* 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. Display update and audio " - "input support will be disabled."); - - /* Init display update plugin if "drdynvc" was loaded successfully */ - else { #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT - /* Load "disp" plugin for display update */ - if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) - guac_rdp_disp_load_plugin(instance->context); + /* Load "disp" plugin for display update */ + if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) + guac_rdp_disp_load_plugin(instance->context, dvc_list); #endif - /* Load "AUDIO_INPUT" plugin for audio input*/ - if (settings->enable_audio_input) { - rdp_client->audio_input = guac_rdp_audio_buffer_alloc(); - guac_rdp_audio_load_plugin(instance->context); - } - - } - - } /* end if drdynvc required */ + /* Load "AUDIO_INPUT" plugin for audio input*/ + if (settings->enable_audio_input) { + rdp_client->audio_input = guac_rdp_audio_buffer_alloc(); + guac_rdp_audio_load_plugin(instance->context, dvc_list); + } /* Load clipboard plugin */ if (freerdp_channels_load_plugin(channels, instance->settings, @@ -338,6 +323,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { } + /* Load DRDYNVC plugin if required */ + if (guac_rdp_load_drdynvc(instance->context, dvc_list)) + guac_client_log(client, GUAC_LOG_WARNING, + "Failed to load drdynvc plugin. Display update and audio " + "input support will be disabled."); + + /* Dynamic virtual channel list is no longer needed */ + guac_rdp_dvc_list_free(dvc_list); + /* Init color conversion structure */ clrconv = calloc(1, sizeof(CLRCONV)); clrconv->alpha = 1; diff --git a/src/protocols/rdp/rdp_disp.c b/src/protocols/rdp/rdp_disp.c index eb142867..7c7e059d 100644 --- a/src/protocols/rdp/rdp_disp.c +++ b/src/protocols/rdp/rdp_disp.c @@ -19,6 +19,7 @@ #include "config.h" #include "client.h" +#include "dvc.h" #include "rdp.h" #include "rdp_disp.h" #include "rdp_settings.h" @@ -54,20 +55,14 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) { free(disp); } -void guac_rdp_disp_load_plugin(rdpContext* context) { +void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list) { -#ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT #ifdef HAVE_RDPSETTINGS_SUPPORTDISPLAYCONTROL context->settings->SupportDisplayControl = TRUE; #endif /* Add "disp" channel */ - ADDIN_ARGV* args = malloc(sizeof(ADDIN_ARGV)); - args->argc = 1; - args->argv = malloc(sizeof(char**) * 1); - args->argv[0] = strdup("disp"); - freerdp_dynamic_channel_collection_add(context->settings, args); -#endif + guac_rdp_dvc_list_add(list, "disp", NULL); } diff --git a/src/protocols/rdp/rdp_disp.h b/src/protocols/rdp/rdp_disp.h index 3378cf76..093e7ed4 100644 --- a/src/protocols/rdp/rdp_disp.h +++ b/src/protocols/rdp/rdp_disp.h @@ -20,6 +20,7 @@ #ifndef GUAC_RDP_DISP_H #define GUAC_RDP_DISP_H +#include "dvc.h" #include "rdp_settings.h" #include @@ -102,7 +103,7 @@ void guac_rdp_disp_free(guac_rdp_disp* disp); * * @param context The rdpContext associated with the active RDP session. */ -void guac_rdp_disp_load_plugin(rdpContext* context); +void guac_rdp_disp_load_plugin(rdpContext* context, guac_rdp_dvc_list* list); #ifdef HAVE_FREERDP_DISPLAY_UPDATE_SUPPORT /**