From 875d51c1ed788b68f93f5bbcbc19595d9a5aeba9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 22 Dec 2019 20:14:44 -0800 Subject: [PATCH] GUACAMOLE-249: Dynamically wrap channel entry points (FreeRDP will refuse to associate the same entry point with multiple channels). --- src/protocols/rdp/.gitignore | 1 + src/protocols/rdp/Makefile.am | 24 +++-- src/protocols/rdp/channels.c | 49 +++++++++- src/protocols/rdp/channels.h | 94 ++++++++++++++++++++ src/protocols/rdp/generate-entry-wrappers.pl | 77 ++++++++++++++++ 5 files changed, 236 insertions(+), 9 deletions(-) create mode 100755 src/protocols/rdp/generate-entry-wrappers.pl diff --git a/src/protocols/rdp/.gitignore b/src/protocols/rdp/.gitignore index 9f87ecb2..5cb764a7 100644 --- a/src/protocols/rdp/.gitignore +++ b/src/protocols/rdp/.gitignore @@ -4,5 +4,6 @@ _generated_runner.c test_rdp # Autogenerated sources +_generated_channel_entry_wrappers.c _generated_keymaps.c diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 8657420e..443ed0b6 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -33,7 +33,8 @@ SUBDIRS = . tests # Main RDP client library # -nodist_libguac_client_rdp_la_SOURCES = \ +nodist_libguac_client_rdp_la_SOURCES = \ + _generated_channel_entry_wrappers.c \ _generated_keymaps.c libguac_client_rdp_la_SOURCES = \ @@ -200,11 +201,16 @@ libguac_client_rdp_la_LIBADD += @COMMON_SSH_LTLIB@ endif # -# Autogenerate keymaps +# Autogenerated keymaps and channel wrapper functions # -CLEANFILES = _generated_keymaps.c -BUILT_SOURCES = _generated_keymaps.c +CLEANFILES = \ + _generated_channel_entry_wrappers.c \ + _generated_keymaps.c + +BUILT_SOURCES = \ + _generated_channel_entry_wrappers.c \ + _generated_keymaps.c rdp_keymaps = \ $(srcdir)/keymaps/base.keymap \ @@ -224,9 +230,13 @@ rdp_keymaps = \ $(srcdir)/keymaps/tr_tr_qwerty.keymap _generated_keymaps.c: $(rdp_keymaps) - $(srcdir)/keymaps/generate.pl $(rdp_keymaps) + $(AM_V_GEN) $(srcdir)/keymaps/generate.pl $(rdp_keymaps) -EXTRA_DIST = \ - $(rdp_keymaps) \ +_generated_channel_entry_wrappers.c: $(srcdir)/channels.h $(srcdir)/generate-entry-wrappers.pl + $(AM_V_GEN) $(srcdir)/generate-entry-wrappers.pl $(srcdir)/channels.h + +EXTRA_DIST = \ + $(rdp_keymaps) \ + generate-entry-wrappers.pl \ keymaps/generate.pl diff --git a/src/protocols/rdp/channels.c b/src/protocols/rdp/channels.c index 63512fe0..368846a8 100644 --- a/src/protocols/rdp/channels.c +++ b/src/protocols/rdp/channels.c @@ -18,12 +18,53 @@ */ #include "config.h" +#include "channels.h" #include "rdp.h" #include #include #include +int guac_rdp_wrapped_entry_ex_count = 0; + +int guac_rdp_wrapped_entry_count = 0; + +PVIRTUALCHANNELENTRYEX guac_rdp_wrapped_entry_ex[GUAC_RDP_MAX_CHANNELS] = { NULL }; + +PVIRTUALCHANNELENTRY guac_rdp_wrapped_entry[GUAC_RDP_MAX_CHANNELS] = { NULL }; + +PVIRTUALCHANNELENTRYEX guac_rdp_plugin_wrap_entry_ex(PVIRTUALCHANNELENTRYEX entry_ex) { + + /* Do not wrap if there is insufficient space to store the wrapped + * function */ + if (guac_rdp_wrapped_entry_ex_count == GUAC_RDP_MAX_CHANNELS) + return entry_ex; + + /* Generate wrapped version of provided entry point */ + PVIRTUALCHANNELENTRYEX wrapper = guac_rdp_entry_ex_wrappers[guac_rdp_wrapped_entry_ex_count]; + guac_rdp_wrapped_entry_ex[guac_rdp_wrapped_entry_ex_count] = entry_ex; + guac_rdp_wrapped_entry_ex_count++; + + return wrapper; + +} + +PVIRTUALCHANNELENTRY guac_rdp_plugin_wrap_entry(PVIRTUALCHANNELENTRY entry) { + + /* Do not wrap if there is insufficient space to store the wrapped + * function */ + if (guac_rdp_wrapped_entry_count == GUAC_RDP_MAX_CHANNELS) + return entry; + + /* Generate wrapped version of provided entry point */ + PVIRTUALCHANNELENTRY wrapper = guac_rdp_entry_wrappers[guac_rdp_wrapped_entry_count]; + guac_rdp_wrapped_entry[guac_rdp_wrapped_entry_count] = entry; + guac_rdp_wrapped_entry_count++; + + return wrapper; + +} + int guac_freerdp_channels_load_plugin(rdpChannels* channels, rdpSettings* settings, const char* name, void* data) { @@ -31,15 +72,19 @@ int guac_freerdp_channels_load_plugin(rdpChannels* channels, PVIRTUALCHANNELENTRYEX entry_ex = (PVIRTUALCHANNELENTRYEX) (void*) freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); - if (entry_ex != NULL) + if (entry_ex != NULL) { + entry_ex = guac_rdp_plugin_wrap_entry_ex(entry_ex); return freerdp_channels_client_load_ex(channels, settings, entry_ex, data); + } /* Lacking the "ex" entry point, attempt to load using the non-ex version */ PVIRTUALCHANNELENTRY entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); - if (entry != NULL) + if (entry != NULL) { + entry = guac_rdp_plugin_wrap_entry(entry); return freerdp_channels_client_load(channels, settings, entry, data); + } /* The plugin does not exist / cannot be loaded */ return 1; diff --git a/src/protocols/rdp/channels.h b/src/protocols/rdp/channels.h index d8a8f619..d31a685b 100644 --- a/src/protocols/rdp/channels.h +++ b/src/protocols/rdp/channels.h @@ -25,6 +25,22 @@ #include #include +/** + * The maximum number of static channels supported by Guacamole's RDP support. + * This value should be given a value which is at least the value of FreeRDP's + * CHANNEL_MAX_COUNT. + * + * NOTE: The value of this macro must be specified statically (not as a + * reference to CHANNEL_MAX_COUNT), as its value is extracted and used by the + * entry point wrapper code generator (generate-entry-wrappers.pl). + */ +#define GUAC_RDP_MAX_CHANNELS 64 + +/* Validate GUAC_RDP_MAX_CHANNELS is sane at compile time */ +#if GUAC_RDP_MAX_CHANNELS < CHANNEL_MAX_COUNT +#error "GUAC_RDP_MAX_CHANNELS must not be less than CHANNEL_MAX_COUNT" +#endif + /** * Loads the FreeRDP plugin having the given name. This function is a drop-in * replacement for freerdp_channels_load_plugin() which additionally loads @@ -105,5 +121,83 @@ int guac_freerdp_channels_load_plugin(rdpChannels* channels, void guac_freerdp_dynamic_channel_collection_add(rdpSettings* settings, const char* name, ...); +/** + * The number of wrapped channel entry points currently stored within + * guac_rdp_wrapped_entry_ex. + */ +extern int guac_rdp_wrapped_entry_ex_count; + +/** + * All currently wrapped entry points that use the PVIRTUALCHANNELENTRYEX + * variant. + */ +extern PVIRTUALCHANNELENTRYEX guac_rdp_wrapped_entry_ex[GUAC_RDP_MAX_CHANNELS]; + +/** + * Lookup table of wrapper functions for PVIRTUALCHANNELENTRYEX entry points. + * Each function within this array is generated at compile time by the entry + * point wrapper code generator (generate-entry-wrappers.pl) and automatically + * invokes the corresponding wrapped entry point stored within + * guac_rdp_wrapped_entry_ex. + */ +extern PVIRTUALCHANNELENTRYEX guac_rdp_entry_ex_wrappers[GUAC_RDP_MAX_CHANNELS]; + +/** + * Wraps the provided entry point function, returning a different entry point + * which simply invokes the original. As long as this function is not invoked + * more than GUAC_RDP_MAX_CHANNELS times, each returned entry point will be + * unique, even if the provided entry point is not. As FreeRDP will refuse to + * load a plugin if its entry point is already loaded, this allows a single + * FreeRDP plugin to be loaded multiple times. + * + * @param entry_ex + * The entry point function to wrap. + * + * @return + * A wrapped version of the provided entry point, or the unwrapped entry + * point if there is insufficient space remaining within + * guac_rdp_entry_ex_wrappers to wrap the entry point. + */ +PVIRTUALCHANNELENTRYEX guac_rdp_plugin_wrap_entry_ex(PVIRTUALCHANNELENTRYEX entry_ex); + +/** + * The number of wrapped channel entry points currently stored within + * guac_rdp_wrapped_entry. + */ +extern int guac_rdp_wrapped_entry_count; + +/** + * All currently wrapped entry points that use the PVIRTUALCHANNELENTRY + * variant. + */ +extern PVIRTUALCHANNELENTRY guac_rdp_wrapped_entry[GUAC_RDP_MAX_CHANNELS]; + +/** + * Lookup table of wrapper functions for PVIRTUALCHANNELENTRY entry points. + * Each function within this array is generated at compile time by the entry + * point wrapper code generator (generate-entry-wrappers.pl) and automatically + * invokes the corresponding wrapped entry point stored within + * guac_rdp_wrapped_entry. + */ +extern PVIRTUALCHANNELENTRY guac_rdp_entry_wrappers[GUAC_RDP_MAX_CHANNELS]; + +/** + * Wraps the provided entry point function, returning a different entry point + * which simply invokes the original. As long as this function is not invoked + * more than GUAC_RDP_MAX_CHANNELS times, each returned entry point will be + * unique, even if the provided entry point is not. As FreeRDP will refuse to + * load a plugin if its entry point is already loaded, this allows a single + * FreeRDP plugin to be loaded multiple times. + * + * @param entry + * The entry point function to wrap. + * + * @return + * A wrapped version of the provided entry point, or the unwrapped entry + * point if there is insufficient space remaining within + * guac_rdp_entry_wrappers to wrap the entry point. + */ +PVIRTUALCHANNELENTRY guac_rdp_plugin_wrap_entry(PVIRTUALCHANNELENTRY entry); + #endif diff --git a/src/protocols/rdp/generate-entry-wrappers.pl b/src/protocols/rdp/generate-entry-wrappers.pl new file mode 100755 index 00000000..21efdbca --- /dev/null +++ b/src/protocols/rdp/generate-entry-wrappers.pl @@ -0,0 +1,77 @@ +#!/usr/bin/env perl +# +# 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. +# + +# +# generate-entry-wrappers.pl +# +# Generates C source which defines wrapper functions for FreeRDP plugin entry +# points, allowing multiple instances of the same plugin to be loaded despite +# otherwise always having the same entry point. +# +# The resulting source is stored within "_generated_channel_entry_wrappers.c". +# + +use strict; + +## +## The maximum number of static channels supported by Guacamole's RDP support. +## +my $GUAC_RDP_MAX_CHANNELS; + +# Extract value of GUAC_RDP_MAX_CHANNELS macro from provided source +while (<>) { + if ((my $value) = m/^\s*#define\s+GUAC_RDP_MAX_CHANNELS\s+(\d+)\s*$/) { + $GUAC_RDP_MAX_CHANNELS = $value; + } +} + +open OUTPUT, ">", "_generated_channel_entry_wrappers.c"; + +# Generate required headers +print OUTPUT <<"EOF"; +#include "channels.h" +#include +#include +EOF + +# Generate wrapper definitions for PVIRTUALCHANNELENTRYEX entry point variant +print OUTPUT <<"EOF" for (1..$GUAC_RDP_MAX_CHANNELS); +static BOOL guac_rdp_plugin_entry_ex_wrapper$_(PCHANNEL_ENTRY_POINTS_EX entry_points_ex, PVOID init_handle) { + return guac_rdp_wrapped_entry_ex[$_ - 1](entry_points_ex, init_handle); +} +EOF + +# Generate wrapper definitions for PVIRTUALCHANNELENTRY entry point variant +print OUTPUT <<"EOF" for (1..$GUAC_RDP_MAX_CHANNELS); +static BOOL guac_rdp_plugin_entry_wrapper$_(PCHANNEL_ENTRY_POINTS entry_points) { + return guac_rdp_wrapped_entry[$_ - 1](entry_points); +} +EOF + +# Populate lookup table of PVIRTUALCHANNELENTRYEX wrapper functions +print OUTPUT "PVIRTUALCHANNELENTRYEX guac_rdp_entry_ex_wrappers[$GUAC_RDP_MAX_CHANNELS] = {\n"; +print OUTPUT " guac_rdp_plugin_entry_ex_wrapper$_,\n" for (1..$GUAC_RDP_MAX_CHANNELS); +print OUTPUT "};\n"; + +# Populate lookup table of PVIRTUALCHANNELENTRY wrapper functions +print OUTPUT "PVIRTUALCHANNELENTRY guac_rdp_entry_wrappers[$GUAC_RDP_MAX_CHANNELS] = {\n"; +print OUTPUT " guac_rdp_plugin_entry_wrapper$_,\n" for (1..$GUAC_RDP_MAX_CHANNELS); +print OUTPUT "};\n"; +