diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /server/proxy/modules | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/proxy/modules')
-rw-r--r-- | server/proxy/modules/CMakeLists.txt | 33 | ||||
-rw-r--r-- | server/proxy/modules/README.md | 66 | ||||
-rw-r--r-- | server/proxy/modules/bitmap-filter/CMakeLists.txt | 60 | ||||
-rw-r--r-- | server/proxy/modules/bitmap-filter/bitmap-filter.cpp | 453 | ||||
-rw-r--r-- | server/proxy/modules/demo/CMakeLists.txt | 53 | ||||
-rw-r--r-- | server/proxy/modules/demo/demo.cpp | 422 | ||||
-rw-r--r-- | server/proxy/modules/dyn-channel-dump/CMakeLists.txt | 58 | ||||
-rw-r--r-- | server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp | 436 |
8 files changed, 1581 insertions, 0 deletions
diff --git a/server/proxy/modules/CMakeLists.txt b/server/proxy/modules/CMakeLists.txt new file mode 100644 index 0000000..500a3e7 --- /dev/null +++ b/server/proxy/modules/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com> +# +# Licensed 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. + +# The third-party directory is meant for third-party components to be built +# as part of the main FreeRDP build system, making separate maintenance easier. +# Subdirectories of the third-party directory are ignored by git, but are +# automatically included by CMake when the -DWITH_THIRD_PARTY=on option is used. + +# include proxy header files for proxy modules +include_directories("${PROJECT_SOURCE_DIR}/server/proxy") +include_directories("${PROJECT_SOURCE_DIR}/server/proxy/modules") + +# taken from FreeRDP/third-party/CMakeLists.txt +file(GLOB all_valid_subdirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/CMakeLists.txt") + +foreach(dir ${all_valid_subdirs}) + if(${dir} MATCHES "^([^/]*)/+CMakeLists.txt") + string(REGEX REPLACE "^([^/]*)/+CMakeLists.txt" "\\1" dir_trimmed ${dir}) + message(STATUS "Adding proxy module ${dir_trimmed}") + add_subdirectory(${dir_trimmed}) + endif() +endforeach(dir) diff --git a/server/proxy/modules/README.md b/server/proxy/modules/README.md new file mode 100644 index 0000000..f1d2206 --- /dev/null +++ b/server/proxy/modules/README.md @@ -0,0 +1,66 @@ +# Proxy module API + +`freerdp-proxy` has an API for hooking/filtering certain events/messages. +A module can register callbacks to events, allowing to record the data and control whether to pass/ignore, or right out drop the connection. + +During startup, the proxy reads its modules from the configuration: + +```ini +[Plugins] +Modules = demo,cap +``` + +These modules are loaded in a best effort manner. Additionally there is a configuration field for modules that must be loaded, +so the proxy refuses to start if they are not found: + +```ini +[Plugins] +Required = demo,cap +``` + +Modules must be installed as shared libraris in the `<base install>/lib/freerdp3/proxy` folder and match the pattern +`proxy-<name>-plugin.<ext>` (e.g. `proxy-demo-plugin.so`) to be found. +For security reasons loading by full path is not supported and only the installation path is used for lookup. + +## Currently supported hook events + +### Client + +* ClientInitConnect: Called before the client tries to open a connection +* ClientUninitConnect: Called after the client has disconnected +* ClientPreConnect: Called in client PreConnect callback +* ClientPostConnect: Called in client PostConnect callback +* ClientPostDisconnect: Called in client PostDisconnect callback +* ClientX509Certificate: Called in client X509 certificate verification callback +* ClientLoginFailure: Called in client login failure callback +* ClientEndPaint: Called in client EndPaint callback + +### Server + +* ServerPostConnect: Called after a client has connected +* ServerPeerActivate: Called after a client has activated +* ServerChannelsInit: Called after channels are initialized +* ServerChannelsFree: Called after channels are cleaned up +* ServerSessionEnd: Called after the client connection disconnected + +## Currently supported filter events + +* KeyboardEvent: Keyboard event, e.g. all key press and release events +* MouseEvent: Mouse event, e.g. mouse movement and button press/release events +* ClientChannelData: Client static channel data +* ServerChannelData: Server static channel data +* DynamicChannelCreate: Dynamic channel create +* ServerFetchTargetAddr: Fetch target address (e.g. RDP TargetInfo) +* ServerPeerLogon: A peer is logging on + +## Developing a new module +* Create a new file that includes `freerdp/server/proxy/proxy_modules_api.h`. +* Implement the `proxy_module_entry_point` function and register the callbacks you are interested in. +* Each callback receives two parameters: + * `connectionInfo* info` holds connection info of the raised event. + * `void* param` holds the actual event data. It should be casted by the filter to the suitable struct from `filters_api.h`. +* Each callback must return a `BOOL`: + * `FALSE`: The event will not be proxied. + * `TRUE`: The event will be proxied. + +A demo can be found in `filter_demo.c`. diff --git a/server/proxy/modules/bitmap-filter/CMakeLists.txt b/server/proxy/modules/bitmap-filter/CMakeLists.txt new file mode 100644 index 0000000..d2cc03b --- /dev/null +++ b/server/proxy/modules/bitmap-filter/CMakeLists.txt @@ -0,0 +1,60 @@ +# +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server Demo C++ Module +# +# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com> +# Copyright 2021 Armin Novak <anovak@thincast.com> +# Copyright 2021 Thincast Technologies GmbH +# +# Licensed 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. +# + +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if (NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(proxy-bitmap-filter-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +project(proxy-bitmap-filter-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/) +include(CommonConfigOptions) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(${PROJECT_NAME} SHARED + bitmap-filter.cpp +) + +target_link_libraries(${PROJECT_NAME} winpr freerdp) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1) + +install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR}) diff --git a/server/proxy/modules/bitmap-filter/bitmap-filter.cpp b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp new file mode 100644 index 0000000..d1b2e78 --- /dev/null +++ b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp @@ -0,0 +1,453 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server persist-bitmap-filter Module + * + * this module is designed to deactivate all persistent bitmap cache settings a + * client might send. + * + * Copyright 2023 Armin Novak <anovak@thincast.com> + * Copyright 2023 Thincast Technologies GmbH + * + * Licensed 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 <iostream> +#include <vector> +#include <string> +#include <algorithm> +#include <map> +#include <memory> +#include <mutex> + +#include <freerdp/server/proxy/proxy_modules_api.h> +#include <freerdp/server/proxy/proxy_context.h> + +#include <freerdp/channels/drdynvc.h> +#include <freerdp/channels/rdpgfx.h> +#include <freerdp/utils/gfx.h> + +#define TAG MODULE_TAG("persist-bitmap-filter") + +static constexpr char plugin_name[] = "bitmap-filter"; +static constexpr char plugin_desc[] = + "this plugin deactivates and filters persistent bitmap cache."; + +static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME }; +static const std::vector<std::string> plugin_dyn_intercept = { RDPGFX_DVC_CHANNEL_NAME }; + +class DynChannelState +{ + + public: + bool skip() const + { + return _toSkip != 0; + } + + bool skip(size_t s) + { + if (s > _toSkip) + _toSkip = 0; + else + _toSkip -= s; + return skip(); + } + + size_t remaining() const + { + return _toSkip; + } + + size_t total() const + { + return _totalSkipSize; + } + + void setSkipSize(size_t len) + { + _toSkip = _totalSkipSize = len; + } + + bool drop() const + { + return _drop; + } + + void setDrop(bool d) + { + _drop = d; + } + + uint32_t channelId() const + { + return _channelId; + } + + void setChannelId(uint32_t id) + { + _channelId = id; + } + + private: + size_t _toSkip = 0; + size_t _totalSkipSize = 0; + bool _drop = false; + uint32_t _channelId = 0; +}; + +static BOOL filter_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(pdata->pc); + WINPR_ASSERT(custom); + + auto settings = pdata->pc->context.settings; + + /* We do not want persistent bitmap cache to be used with proxy */ + return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, FALSE); +} + +static BOOL filter_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + auto intercept = std::find(plugin_dyn_intercept.begin(), plugin_dyn_intercept.end(), + data->name) != plugin_dyn_intercept.end(); + if (intercept) + data->intercept = TRUE; + return TRUE; +} + +static BOOL filter_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(), + data->name) != plugin_static_intercept.end(); + if (intercept) + data->intercept = TRUE; + return TRUE; +} + +static size_t drdynvc_cblen_to_bytes(UINT8 cbLen) +{ + switch (cbLen) + { + case 0: + return 1; + + case 1: + return 2; + + default: + return 4; + } +} + +static UINT32 drdynvc_read_variable_uint(wStream* s, UINT8 cbLen) +{ + UINT32 val = 0; + + switch (cbLen) + { + case 0: + Stream_Read_UINT8(s, val); + break; + + case 1: + Stream_Read_UINT16(s, val); + break; + + default: + Stream_Read_UINT32(s, val); + break; + } + + return val; +} + +static BOOL drdynvc_try_read_header(wStream* s, size_t& channelId, size_t& length) +{ + UINT8 value = 0; + Stream_SetPosition(s, 0); + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + Stream_Read_UINT8(s, value); + + const UINT8 cmd = (value & 0xf0) >> 4; + const UINT8 Sp = (value & 0x0c) >> 2; + const UINT8 cbChId = (value & 0x03); + + switch (cmd) + { + case DATA_PDU: + case DATA_FIRST_PDU: + break; + default: + return FALSE; + } + + const size_t channelIdLen = drdynvc_cblen_to_bytes(cbChId); + if (Stream_GetRemainingLength(s) < channelIdLen) + return FALSE; + + channelId = drdynvc_read_variable_uint(s, cbChId); + length = Stream_Length(s); + if (cmd == DATA_FIRST_PDU) + { + const size_t dataLen = drdynvc_cblen_to_bytes(Sp); + if (Stream_GetRemainingLength(s) < dataLen) + return FALSE; + + length = drdynvc_read_variable_uint(s, Sp); + } + + return TRUE; +} + +static DynChannelState* filter_get_plugin_data(proxyPlugin* plugin, proxyData* pdata) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto mgr = static_cast<proxyPluginsManager*>(plugin->custom); + WINPR_ASSERT(mgr); + + WINPR_ASSERT(mgr->GetPluginData); + return static_cast<DynChannelState*>(mgr->GetPluginData(mgr, plugin_name, pdata)); +} + +static BOOL filter_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, DynChannelState* data) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto mgr = static_cast<proxyPluginsManager*>(plugin->custom); + WINPR_ASSERT(mgr); + + WINPR_ASSERT(mgr->SetPluginData); + return mgr->SetPluginData(mgr, plugin_name, pdata, data); +} + +static UINT8 drdynvc_value_to_cblen(UINT32 value) +{ + if (value <= 0xFF) + return 0; + if (value <= 0xFFFF) + return 1; + return 2; +} + +static BOOL drdynvc_write_variable_uint(wStream* s, UINT32 value, UINT8 cbLen) +{ + switch (cbLen) + { + case 0: + Stream_Write_UINT8(s, static_cast<UINT8>(value)); + break; + + case 1: + Stream_Write_UINT16(s, static_cast<UINT16>(value)); + break; + + default: + Stream_Write_UINT32(s, value); + break; + } + + return TRUE; +} + +static BOOL drdynvc_write_header(wStream* s, UINT32 channelId) +{ + const UINT8 cbChId = drdynvc_value_to_cblen(channelId); + const UINT8 value = (DATA_PDU << 4) | cbChId; + const size_t len = drdynvc_cblen_to_bytes(cbChId) + 1; + + if (!Stream_EnsureRemainingCapacity(s, len)) + return FALSE; + + Stream_Write_UINT8(s, value); + return drdynvc_write_variable_uint(s, value, cbChId); +} + +static BOOL filter_forward_empty_offer(const char* sessionID, proxyDynChannelInterceptData* data, + size_t startPosition, UINT32 channelId) +{ + WINPR_ASSERT(data); + + Stream_SetPosition(data->data, startPosition); + if (!drdynvc_write_header(data->data, channelId)) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16))) + return FALSE; + Stream_Write_UINT16(data->data, 0); + Stream_SealLength(data->data); + + WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name, + rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER)); + data->rewritten = TRUE; + return TRUE; +} + +static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyDynChannelInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + data->result = PF_CHANNEL_RESULT_PASS; + if (!data->isBackData && + (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0)) + { + auto state = filter_get_plugin_data(plugin, pdata); + if (!state) + { + WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id, + plugin_name); + return FALSE; + } + const size_t inputDataLength = Stream_Length(data->data); + UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000; + + const auto pos = Stream_GetPosition(data->data); + if (!state->skip()) + { + if (data->first) + { + size_t channelId = 0; + size_t length = 0; + if (drdynvc_try_read_header(data->data, channelId, length)) + { + if (Stream_GetRemainingLength(data->data) >= 2) + { + Stream_Read_UINT16(data->data, cmdId); + state->setSkipSize(length); + state->setDrop(false); + } + } + + switch (cmdId) + { + case RDPGFX_CMDID_CACHEIMPORTOFFER: + state->setDrop(true); + state->setChannelId(channelId); + break; + default: + break; + } + Stream_SetPosition(data->data, pos); + } + } + + if (state->skip()) + { + state->skip(inputDataLength); + if (state->drop()) + { + WLog_WARN(TAG, + "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz + ", remaining: %" PRIuz "]", + pdata->session_id, plugin_name, + rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(), + inputDataLength, state->remaining()); + data->result = PF_CHANNEL_RESULT_DROP; + +#if 0 // TODO: Sending this does screw up some windows RDP server versions :/ + if (state->remaining() == 0) + { + if (!filter_forward_empty_offer(pdata->session_id, data, pos, + state->channelId())) + return FALSE; + } +#endif + } + } + } + + return TRUE; +} + +static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto state = filter_get_plugin_data(plugin, pdata); + delete state; + + auto newstate = new DynChannelState(); + if (!filter_set_plugin_data(plugin, pdata, newstate)) + { + delete newstate; + return FALSE; + } + + return TRUE; +} + +static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto state = filter_get_plugin_data(plugin, pdata); + delete state; + filter_set_plugin_data(plugin, pdata, nullptr); + return TRUE; +} + +#ifdef __cplusplus +extern "C" +{ +#endif + FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata); +#ifdef __cplusplus +} +#endif + +BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata) +{ + proxyPlugin plugin = {}; + + plugin.name = plugin_name; + plugin.description = plugin_desc; + + plugin.ServerSessionStarted = filter_server_session_started; + plugin.ServerSessionEnd = filter_server_session_end; + + plugin.ClientPreConnect = filter_client_pre_connect; + + plugin.StaticChannelToIntercept = filter_static_channel_intercept_list; + plugin.DynChannelToIntercept = filter_dyn_channel_intercept_list; + plugin.DynChannelIntercept = filter_dyn_channel_intercept; + + plugin.custom = plugins_manager; + if (!plugin.custom) + return FALSE; + plugin.userdata = userdata; + + return plugins_manager->RegisterPlugin(plugins_manager, &plugin); +} diff --git a/server/proxy/modules/demo/CMakeLists.txt b/server/proxy/modules/demo/CMakeLists.txt new file mode 100644 index 0000000..bdd85a3 --- /dev/null +++ b/server/proxy/modules/demo/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server Demo C++ Module +# +# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com> +# Copyright 2021 Armin Novak <anovak@thincast.com> +# Copyright 2021 Thincast Technologies GmbH +# +# Licensed 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. +# + +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if (NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(proxy-demo-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/) +include(CommonConfigOptions) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(${PROJECT_NAME} SHARED + demo.cpp +) + +target_link_libraries(${PROJECT_NAME} winpr) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1) + +install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR}) diff --git a/server/proxy/modules/demo/demo.cpp b/server/proxy/modules/demo/demo.cpp new file mode 100644 index 0000000..75526ef --- /dev/null +++ b/server/proxy/modules/demo/demo.cpp @@ -0,0 +1,422 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server Demo C++ Module + * + * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com> + * Copyright 2021 Armin Novak <anovak@thincast.com> + * Copyright 2021 Thincast Technologies GmbH + * + * Licensed 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 <iostream> + +#include <freerdp/api.h> +#include <freerdp/scancode.h> +#include <freerdp/server/proxy/proxy_modules_api.h> + +#define TAG MODULE_TAG("demo") + +struct demo_custom_data +{ + proxyPluginsManager* mgr; + int somesetting; +}; + +static constexpr char plugin_name[] = "demo"; +static constexpr char plugin_desc[] = "this is a test plugin"; + +static BOOL demo_plugin_unload(proxyPlugin* plugin) +{ + WINPR_ASSERT(plugin); + + std::cout << "C++ demo plugin: unloading..." << std::endl; + + /* Here we have to free up our custom data storage. */ + if (plugin) + delete static_cast<struct demo_custom_data*>(plugin->custom); + + return TRUE; +} + +static BOOL demo_client_init_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_uninit_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_post_disconnect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_x509_certificate(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_login_failure(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_end_paint(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_redirect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_peer_activate(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_channels_init(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_channels_free(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_session_end(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_filter_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + proxyPluginsManager* mgr = nullptr; + auto event_data = static_cast<const proxyKeyboardEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + mgr = plugin->mgr; + WINPR_ASSERT(mgr); + + if (event_data == nullptr) + return FALSE; + + if (event_data->rdp_scan_code == RDP_SCANCODE_KEY_B) + { + /* user typed 'B', that means bye :) */ + std::cout << "C++ demo plugin: aborting connection" << std::endl; + mgr->AbortConnect(mgr, pdata); + } + + return TRUE; +} + +static BOOL demo_filter_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + proxyPluginsManager* mgr = nullptr; + auto event_data = static_cast<const proxyUnicodeEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + mgr = plugin->mgr; + WINPR_ASSERT(mgr); + + if (event_data == nullptr) + return FALSE; + + if (event_data->code == 'b') + { + /* user typed 'B', that means bye :) */ + std::cout << "C++ demo plugin: aborting connection" << std::endl; + mgr->AbortConnect(mgr, pdata); + } + + return TRUE; +} + +static BOOL demo_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto event_data = static_cast<const proxyMouseEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto event_data = static_cast<const proxyMouseExEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_INFO(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id, + channel->data_len); + return TRUE; +} + +static BOOL demo_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_WARN(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id, + channel->data_len); + return TRUE; +} + +static BOOL demo_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_WARN(TAG, "%s [0x%04" PRIx16 "]", channel->channel_name, channel->channel_id); + return TRUE; +} + +static BOOL demo_server_fetch_target_addr(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto event_data = static_cast<const proxyFetchTargetEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_peer_logon(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto info = static_cast<const proxyServerPeerLogon*>(param); + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(info); + WINPR_ASSERT(info->identity); + + WLog_INFO(TAG, "%d", info->automatic); + return TRUE; +} + +static BOOL demo_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + WLog_INFO(TAG, "%s", __func__); + return TRUE; +} + +static BOOL demo_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + WLog_INFO(TAG, "%s", __func__); + return TRUE; +} + +static BOOL demo_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyDynChannelInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + WLog_INFO(TAG, "%s", __func__); + return TRUE; +} + +#ifdef __cplusplus +extern "C" +{ +#endif + FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata); +#ifdef __cplusplus +} +#endif + +BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata) +{ + struct demo_custom_data* custom = nullptr; + proxyPlugin plugin = {}; + + plugin.name = plugin_name; + plugin.description = plugin_desc; + plugin.PluginUnload = demo_plugin_unload; + plugin.ClientInitConnect = demo_client_init_connect; + plugin.ClientUninitConnect = demo_client_uninit_connect; + plugin.ClientPreConnect = demo_client_pre_connect; + plugin.ClientPostConnect = demo_client_post_connect; + plugin.ClientPostDisconnect = demo_client_post_disconnect; + plugin.ClientX509Certificate = demo_client_x509_certificate; + plugin.ClientLoginFailure = demo_client_login_failure; + plugin.ClientEndPaint = demo_client_end_paint; + plugin.ClientRedirect = demo_client_redirect; + plugin.ServerPostConnect = demo_server_post_connect; + plugin.ServerPeerActivate = demo_server_peer_activate; + plugin.ServerChannelsInit = demo_server_channels_init; + plugin.ServerChannelsFree = demo_server_channels_free; + plugin.ServerSessionEnd = demo_server_session_end; + plugin.KeyboardEvent = demo_filter_keyboard_event; + plugin.UnicodeEvent = demo_filter_unicode_event; + plugin.MouseEvent = demo_mouse_event; + plugin.MouseExEvent = demo_mouse_ex_event; + plugin.ClientChannelData = demo_client_channel_data; + plugin.ServerChannelData = demo_server_channel_data; + plugin.DynamicChannelCreate = demo_dynamic_channel_create; + plugin.ServerFetchTargetAddr = demo_server_fetch_target_addr; + plugin.ServerPeerLogon = demo_server_peer_logon; + + plugin.StaticChannelToIntercept = demo_static_channel_intercept_list; + plugin.DynChannelToIntercept = demo_dyn_channel_intercept_list; + plugin.DynChannelIntercept = demo_dyn_channel_intercept; + + plugin.userdata = userdata; + + custom = new (struct demo_custom_data); + if (!custom) + return FALSE; + + custom->mgr = plugins_manager; + custom->somesetting = 42; + + plugin.custom = custom; + plugin.userdata = userdata; + + return plugins_manager->RegisterPlugin(plugins_manager, &plugin); +} diff --git a/server/proxy/modules/dyn-channel-dump/CMakeLists.txt b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt new file mode 100644 index 0000000..dc0fc53 --- /dev/null +++ b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt @@ -0,0 +1,58 @@ +# +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server Demo C++ Module +# +# Copyright 2023 Armin Novak <anovak@thincast.com> +# Copyright 2023 Thincast Technologies GmbH +# +# Licensed 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. +# + +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if (NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(proxy-dyn-channel-dump-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/) +include(CommonConfigOptions) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(${PROJECT_NAME} SHARED + dyn-channel-dump.cpp +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + winpr + freerdp + freerdp-client + freerdp-server + freerdp-server-proxy +) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1) + +install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR}) diff --git a/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp new file mode 100644 index 0000000..d80902f --- /dev/null +++ b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp @@ -0,0 +1,436 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server persist-bitmap-filter Module + * + * this module is designed to deactivate all persistent bitmap cache settings a + * client might send. + * + * Copyright 2023 Armin Novak <anovak@thincast.com> + * Copyright 2023 Thincast Technologies GmbH + * + * Licensed 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 <fstream> +#include <iostream> +#include <regex> +#include <vector> +#include <sstream> +#include <string> +#include <algorithm> +#include <map> +#include <memory> +#include <mutex> +#include <atomic> +#if __has_include(<filesystem>) +#include <filesystem> +namespace fs = std::filesystem; +#elif __has_include(<experimental/filesystem>) +#include <experimental/filesystem> +namespace fs = std::experimental::filesystem; +#else +#error Could not find system header "<filesystem>" or "<experimental/filesystem>" +#endif + +#include <freerdp/server/proxy/proxy_modules_api.h> +#include <freerdp/server/proxy/proxy_context.h> + +#include <freerdp/channels/drdynvc.h> +#include <freerdp/channels/rdpgfx.h> +#include <freerdp/utils/gfx.h> + +#define TAG MODULE_TAG("dyn-channel-dump") + +static constexpr char plugin_name[] = "dyn-channel-dump"; +static constexpr char plugin_desc[] = + "This plugin dumps configurable dynamic channel data to a file."; + +static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME }; + +static constexpr char key_path[] = "path"; +static constexpr char key_channels[] = "channels"; + +class PluginData +{ + public: + PluginData(proxyPluginsManager* mgr) : _mgr(mgr), _sessionid(0) + { + } + + proxyPluginsManager* mgr() const + { + return _mgr; + } + + uint64_t session() + { + return _sessionid++; + } + + private: + proxyPluginsManager* _mgr; + uint64_t _sessionid; +}; + +class ChannelData +{ + public: + ChannelData(const std::string& base, const std::vector<std::string>& list, uint64_t sessionid) + : _base(base), _channels_to_dump(list), _session_id(sessionid) + { + char str[64] = {}; + _snprintf(str, sizeof(str), "session-%016" PRIx64, _session_id); + _base /= str; + } + + bool add(const std::string& name, bool back) + { + std::lock_guard<std::mutex> guard(_mux); + if (_map.find(name) == _map.end()) + { + WLog_INFO(TAG, "adding '%s' to dump list", name.c_str()); + _map.insert({ name, 0 }); + } + return true; + } + + std::ofstream stream(const std::string& name, bool back) + { + std::lock_guard<std::mutex> guard(_mux); + auto& atom = _map[name]; + auto count = atom++; + auto path = filepath(name, back, count); + WLog_DBG(TAG, "[%s] writing file '%s'", name.c_str(), path.c_str()); + return std::ofstream(path); + } + + bool dump_enabled(const std::string& name) const + { + if (name.empty()) + { + WLog_WARN(TAG, "empty dynamic channel name, skipping"); + return false; + } + + auto enabled = std::find(_channels_to_dump.begin(), _channels_to_dump.end(), name) != + _channels_to_dump.end(); + WLog_DBG(TAG, "channel '%s' dumping %s", name.c_str(), enabled ? "enabled" : "disabled"); + return enabled; + } + + bool ensure_path_exists() + { + if (!fs::exists(_base)) + { + if (!fs::create_directories(_base)) + { + WLog_ERR(TAG, "Failed to create dump directory %s", _base.c_str()); + return false; + } + } + else if (!fs::is_directory(_base)) + { + WLog_ERR(TAG, "dump path %s is not a directory", _base.c_str()); + return false; + } + return true; + } + + bool create() + { + if (!ensure_path_exists()) + return false; + + if (_channels_to_dump.empty()) + { + WLog_ERR(TAG, "Empty configuration entry [%s/%s], can not continue", plugin_name, + key_channels); + return false; + } + return true; + } + + uint64_t session() const + { + return _session_id; + } + + private: + fs::path filepath(const std::string& channel, bool back, uint64_t count) const + { + auto name = idstr(channel, back); + char cstr[32] = {}; + _snprintf(cstr, sizeof(cstr), "%016" PRIx64 "-", count); + auto path = _base / cstr; + path += name; + path += ".dump"; + return path; + } + + std::string idstr(const std::string& name, bool back) const + { + std::stringstream ss; + ss << name << "."; + if (back) + ss << "back"; + else + ss << "front"; + return ss.str(); + } + + private: + fs::path _base; + std::vector<std::string> _channels_to_dump; + + std::mutex _mux; + std::map<std::string, uint64_t> _map; + uint64_t _session_id; +}; + +static PluginData* dump_get_plugin_data(proxyPlugin* plugin) +{ + WINPR_ASSERT(plugin); + + auto plugindata = static_cast<PluginData*>(plugin->custom); + WINPR_ASSERT(plugindata); + return plugindata; +} + +static ChannelData* dump_get_plugin_data(proxyPlugin* plugin, proxyData* pdata) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto plugindata = dump_get_plugin_data(plugin); + WINPR_ASSERT(plugindata); + + auto mgr = plugindata->mgr(); + WINPR_ASSERT(mgr); + + WINPR_ASSERT(mgr->GetPluginData); + return static_cast<ChannelData*>(mgr->GetPluginData(mgr, plugin_name, pdata)); +} + +static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, ChannelData* data) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto plugindata = dump_get_plugin_data(plugin); + WINPR_ASSERT(plugindata); + + auto mgr = plugindata->mgr(); + WINPR_ASSERT(mgr); + + auto cdata = dump_get_plugin_data(plugin, pdata); + delete cdata; + + WINPR_ASSERT(mgr->SetPluginData); + return mgr->SetPluginData(mgr, plugin_name, pdata, data); +} + +static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata, const std::string& name) +{ + auto config = dump_get_plugin_data(plugin, pdata); + if (!config) + { + WLog_ERR(TAG, "Missing channel data"); + return false; + } + return config->dump_enabled(name); +} + +static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + data->intercept = dump_channel_enabled(plugin, pdata, data->name); + if (data->intercept) + { + auto cdata = dump_get_plugin_data(plugin, pdata); + if (!cdata) + return FALSE; + + if (!cdata->add(data->name, false)) + { + WLog_ERR(TAG, "failed to create files for '%s'", data->name); + } + if (!cdata->add(data->name, true)) + { + WLog_ERR(TAG, "failed to create files for '%s'", data->name); + } + WLog_INFO(TAG, "Dumping channel '%s'", data->name); + } + return TRUE; +} + +static BOOL dump_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(), + data->name) != plugin_static_intercept.end(); + if (intercept) + { + WLog_INFO(TAG, "intercepting channel '%s'", data->name); + data->intercept = TRUE; + } + + return TRUE; +} + +static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyDynChannelInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + data->result = PF_CHANNEL_RESULT_PASS; + if (dump_channel_enabled(plugin, pdata, data->name)) + { + WLog_DBG(TAG, "intercepting channel '%s'", data->name); + auto cdata = dump_get_plugin_data(plugin, pdata); + if (!cdata) + { + WLog_ERR(TAG, "Missing channel data"); + return FALSE; + } + + if (!cdata->ensure_path_exists()) + return FALSE; + + auto stream = cdata->stream(data->name, data->isBackData); + auto buffer = reinterpret_cast<const char*>(Stream_ConstBuffer(data->data)); + if (!stream.is_open() || !stream.good()) + { + WLog_ERR(TAG, "Could not write to stream"); + return FALSE; + } + stream.write(buffer, Stream_Length(data->data)); + if (stream.fail()) + { + WLog_ERR(TAG, "Could not write to stream"); + return FALSE; + } + stream.flush(); + } + + return TRUE; +} + +static std::vector<std::string> split(const std::string& input, const std::string& regex) +{ + // passing -1 as the submatch index parameter performs splitting + std::regex re(regex); + std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 }; + std::sregex_token_iterator last; + return { first, last }; +} + +static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto custom = dump_get_plugin_data(plugin); + WINPR_ASSERT(custom); + + auto config = pdata->config; + WINPR_ASSERT(config); + + auto cpath = pf_config_get(config, plugin_name, key_path); + if (!cpath) + { + WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name, + key_path); + return FALSE; + } + auto cchannels = pf_config_get(config, plugin_name, key_channels); + if (!cchannels) + { + WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name, + key_channels); + return FALSE; + } + + std::string path(cpath); + std::string channels(cchannels); + std::vector<std::string> list = split(channels, "[;,]"); + auto cfg = new ChannelData(path, list, custom->session()); + if (!cfg || !cfg->create()) + { + delete cfg; + return FALSE; + } + + dump_set_plugin_data(plugin, pdata, cfg); + + WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session()); + return TRUE; +} + +static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto cfg = dump_get_plugin_data(plugin, pdata); + if (cfg) + WLog_DBG(TAG, "ending session dump %" PRIu64, cfg->session()); + dump_set_plugin_data(plugin, pdata, nullptr); + return TRUE; +} + +static BOOL dump_unload(proxyPlugin* plugin) +{ + if (!plugin) + return TRUE; + delete static_cast<PluginData*>(plugin->custom); + return TRUE; +} + +extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, + void* userdata); + +BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata) +{ + proxyPlugin plugin = {}; + + plugin.name = plugin_name; + plugin.description = plugin_desc; + + plugin.PluginUnload = dump_unload; + plugin.ServerSessionStarted = dump_session_started; + plugin.ServerSessionEnd = dump_session_end; + + plugin.StaticChannelToIntercept = dump_static_channel_intercept_list; + plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list; + plugin.DynChannelIntercept = dump_dyn_channel_intercept; + + plugin.custom = new PluginData(plugins_manager); + if (!plugin.custom) + return FALSE; + plugin.userdata = userdata; + + return plugins_manager->RegisterPlugin(plugins_manager, &plugin); +} |