37#if __has_include(<filesystem>)
39namespace fs = std::filesystem;
40#elif __has_include(<experimental/filesystem>)
41#include <experimental/filesystem>
42namespace fs = std::experimental::filesystem;
44#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
47#include <freerdp/server/proxy/proxy_modules_api.h>
48#include <freerdp/server/proxy/proxy_context.h>
50#include <freerdp/channels/drdynvc.h>
51#include <freerdp/channels/rdpgfx.h>
52#include <freerdp/utils/gfx.h>
54#define TAG MODULE_TAG("dyn-channel-dump")
56static constexpr char plugin_name[] =
"dyn-channel-dump";
57static constexpr char plugin_desc[] =
58 "This plugin dumps configurable dynamic channel data to a file.";
60[[nodiscard]]
static const std::vector<std::string>& plugin_static_intercept()
62 static std::vector<std::string> vec;
64 vec.emplace_back(DRDYNVC_SVC_CHANNEL_NAME);
68static constexpr char key_path[] =
"path";
69static constexpr char key_channels[] =
"channels";
74 explicit PluginData(proxyPluginsManager* mgr) : _mgr(mgr)
78 [[nodiscard]] proxyPluginsManager* mgr()
const
89 proxyPluginsManager* _mgr;
90 uint64_t _sessionid{ 0 };
96 ChannelData(
const std::string& base, std::vector<std::string> list, uint64_t sessionid)
97 : _base(base), _channels_to_dump(std::move(list)), _session_id(sessionid)
100 (void)_snprintf(str,
sizeof(str),
"session-%016" PRIx64, _session_id);
104 bool add(
const std::string& name, WINPR_ATTR_UNUSED
bool back)
106 std::scoped_lock guard(_mux);
107 if (_map.find(name) == _map.end())
109 WLog_INFO(TAG,
"adding '%s' to dump list", name.c_str());
110 _map.insert({ name, 0 });
115 std::ofstream stream(
const std::string& name,
bool back)
117 std::scoped_lock guard(_mux);
118 auto& atom = _map[name];
120 auto path = filepath(name, back, count);
121 WLog_DBG(TAG,
"[%s] writing file '%s'", name.c_str(), path.c_str());
122 return std::ofstream(path);
125 [[nodiscard]]
bool dump_enabled(
const std::string& name)
const
129 WLog_WARN(TAG,
"empty dynamic channel name, skipping");
133 auto enabled = std::find(_channels_to_dump.begin(), _channels_to_dump.end(), name) !=
134 _channels_to_dump.end();
135 WLog_DBG(TAG,
"channel '%s' dumping %s", name.c_str(), enabled ?
"enabled" :
"disabled");
139 bool ensure_path_exists()
141 if (!fs::exists(_base))
143 if (!fs::create_directories(_base))
145 WLog_ERR(TAG,
"Failed to create dump directory %s", _base.c_str());
149 else if (!fs::is_directory(_base))
151 WLog_ERR(TAG,
"dump path %s is not a directory", _base.c_str());
159 if (!ensure_path_exists())
162 if (_channels_to_dump.empty())
164 WLog_ERR(TAG,
"Empty configuration entry [%s/%s], can not continue", plugin_name,
171 [[nodiscard]] uint64_t session()
const
177 [[nodiscard]] fs::path filepath(
const std::string& channel,
bool back, uint64_t count)
const
179 auto name = idstr(channel, back);
181 (void)_snprintf(cstr,
sizeof(cstr),
"%016" PRIx64
"-", count);
182 auto path = _base / cstr;
188 [[nodiscard]]
static std::string idstr(
const std::string& name,
bool back)
190 std::stringstream ss;
200 std::vector<std::string> _channels_to_dump;
203 std::map<std::string, uint64_t> _map;
204 uint64_t _session_id;
207[[nodiscard]]
static PluginData* dump_get_plugin_data(proxyPlugin* plugin)
209 WINPR_ASSERT(plugin);
211 auto plugindata =
static_cast<PluginData*
>(plugin->custom);
212 WINPR_ASSERT(plugindata);
216[[nodiscard]]
static ChannelData* dump_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
218 WINPR_ASSERT(plugin);
221 auto plugindata = dump_get_plugin_data(plugin);
222 WINPR_ASSERT(plugindata);
224 auto mgr = plugindata->mgr();
227 WINPR_ASSERT(mgr->GetPluginData);
228 return static_cast<ChannelData*
>(mgr->GetPluginData(mgr, plugin_name, pdata));
231[[nodiscard]]
static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata,
234 WINPR_ASSERT(plugin);
237 auto plugindata = dump_get_plugin_data(plugin);
238 WINPR_ASSERT(plugindata);
240 auto mgr = plugindata->mgr();
243 auto cdata = dump_get_plugin_data(plugin, pdata);
246 WINPR_ASSERT(mgr->SetPluginData);
247 return mgr->SetPluginData(mgr, plugin_name, pdata, data);
250[[nodiscard]]
static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata,
251 const std::string& name)
253 auto config = dump_get_plugin_data(plugin, pdata);
256 WLog_ERR(TAG,
"Missing channel data");
259 return config->dump_enabled(name);
262[[nodiscard]]
static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata,
267 WINPR_ASSERT(plugin);
271 data->intercept = dump_channel_enabled(plugin, pdata, data->name);
274 auto cdata = dump_get_plugin_data(plugin, pdata);
278 if (!cdata->add(data->name,
false))
280 WLog_ERR(TAG,
"failed to create files for '%s'", data->name);
282 if (!cdata->add(data->name,
true))
284 WLog_ERR(TAG,
"failed to create files for '%s'", data->name);
286 WLog_INFO(TAG,
"Dumping channel '%s'", data->name);
291[[nodiscard]]
static BOOL dump_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
292 [[maybe_unused]] proxyData* pdata,
297 WINPR_ASSERT(plugin);
301 auto intercept = std::find(plugin_static_intercept().begin(), plugin_static_intercept().end(),
302 data->name) != plugin_static_intercept().end();
305 WLog_INFO(TAG,
"intercepting channel '%s'", data->name);
306 data->intercept = TRUE;
312[[nodiscard]]
static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata,
317 WINPR_ASSERT(plugin);
321 data->result = PF_CHANNEL_RESULT_PASS;
322 if (dump_channel_enabled(plugin, pdata, data->name))
324 WLog_DBG(TAG,
"intercepting channel '%s'", data->name);
325 auto cdata = dump_get_plugin_data(plugin, pdata);
328 WLog_ERR(TAG,
"Missing channel data");
332 if (!cdata->ensure_path_exists())
335 auto stream = cdata->stream(data->name, data->isBackData);
336 auto buffer =
reinterpret_cast<const char*
>(Stream_ConstBuffer(data->data));
337 if (!stream.is_open() || !stream.good())
339 WLog_ERR(TAG,
"Could not write to stream");
342 const auto s = Stream_Length(data->data);
343 if (s > std::numeric_limits<std::streamsize>::max())
345 WLog_ERR(TAG,
"Stream length %" PRIuz
" exceeds std::streamsize::max", s);
348 stream.write(buffer,
static_cast<std::streamsize
>(s));
351 WLog_ERR(TAG,
"Could not write to stream");
360[[nodiscard]]
static std::vector<std::string> split(
const std::string& input,
361 const std::string& regex)
364 std::regex re(regex);
365 std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
366 std::sregex_token_iterator last;
367 return { first, last };
370[[nodiscard]]
static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata,
373 WINPR_ASSERT(plugin);
376 auto custom = dump_get_plugin_data(plugin);
377 WINPR_ASSERT(custom);
379 auto config = pdata->config;
380 WINPR_ASSERT(config);
385 WLog_ERR(TAG,
"Missing configuration entry [%s/%s], can not continue", plugin_name,
389 auto cchannels =
pf_config_get(config, plugin_name, key_channels);
392 WLog_ERR(TAG,
"Missing configuration entry [%s/%s], can not continue", plugin_name,
397 std::string path(cpath);
398 std::string channels(cchannels);
399 std::vector<std::string> list = split(channels,
"[;,]");
400 auto cfg =
new ChannelData(path, std::move(list), custom->session());
401 if (!cfg || !cfg->create())
407 if (!dump_set_plugin_data(plugin, pdata, cfg))
410 WLog_DBG(TAG,
"starting session dump %" PRIu64, cfg->session());
414[[nodiscard]]
static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata,
void* )
416 WINPR_ASSERT(plugin);
419 auto cfg = dump_get_plugin_data(plugin, pdata);
421 WLog_DBG(TAG,
"ending session dump %" PRIu64, cfg->session());
422 return dump_set_plugin_data(plugin, pdata,
nullptr);
425[[nodiscard]]
static BOOL dump_unload(proxyPlugin* plugin)
429 delete static_cast<PluginData*
>(plugin->custom);
433[[nodiscard]]
static BOOL int_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
436 proxyPlugin plugin = {};
438 plugin.name = plugin_name;
439 plugin.description = plugin_desc;
441 plugin.PluginUnload = dump_unload;
442 plugin.ServerSessionStarted = dump_session_started;
443 plugin.ServerSessionEnd = dump_session_end;
445 plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
446 plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
447 plugin.DynChannelIntercept = dump_dyn_channel_intercept;
449 plugin.custom =
new PluginData(plugins_manager);
452 plugin.userdata = userdata;
454 return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
461#if defined(BUILD_SHARED_LIBS)
463 FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata);
465 BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata)
467 return int_proxy_module_entry_point(plugins_manager, userdata);
471FREERDP_API BOOL dyn_channel_dump_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
473BOOL dyn_channel_dump_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata)
475 return int_proxy_module_entry_point(plugins_manager, userdata);
WINPR_ATTR_NODISCARD FREERDP_API const char * pf_config_get(const proxyConfig *config, const char *section, const char *key)
pf_config_get get a value for a section/key