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.";
60static 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::lock_guard<std::mutex> 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::lock_guard<std::mutex> 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;
207static PluginData* dump_get_plugin_data(proxyPlugin* plugin)
209 WINPR_ASSERT(plugin);
211 auto plugindata =
static_cast<PluginData*
>(plugin->custom);
212 WINPR_ASSERT(plugindata);
216static 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));
231static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, ChannelData* data)
233 WINPR_ASSERT(plugin);
236 auto plugindata = dump_get_plugin_data(plugin);
237 WINPR_ASSERT(plugindata);
239 auto mgr = plugindata->mgr();
242 auto cdata = dump_get_plugin_data(plugin, pdata);
245 WINPR_ASSERT(mgr->SetPluginData);
246 return mgr->SetPluginData(mgr, plugin_name, pdata, data);
249static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata,
const std::string& name)
251 auto config = dump_get_plugin_data(plugin, pdata);
254 WLog_ERR(TAG,
"Missing channel data");
257 return config->dump_enabled(name);
260static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata,
void* arg)
264 WINPR_ASSERT(plugin);
268 data->intercept = dump_channel_enabled(plugin, pdata, data->name);
271 auto cdata = dump_get_plugin_data(plugin, pdata);
275 if (!cdata->add(data->name,
false))
277 WLog_ERR(TAG,
"failed to create files for '%s'", data->name);
279 if (!cdata->add(data->name,
true))
281 WLog_ERR(TAG,
"failed to create files for '%s'", data->name);
283 WLog_INFO(TAG,
"Dumping channel '%s'", data->name);
288static BOOL dump_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
289 [[maybe_unused]] proxyData* pdata,
void* arg)
293 WINPR_ASSERT(plugin);
297 auto intercept = std::find(plugin_static_intercept().begin(), plugin_static_intercept().end(),
298 data->name) != plugin_static_intercept().end();
301 WLog_INFO(TAG,
"intercepting channel '%s'", data->name);
302 data->intercept = TRUE;
308static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata,
void* arg)
312 WINPR_ASSERT(plugin);
316 data->result = PF_CHANNEL_RESULT_PASS;
317 if (dump_channel_enabled(plugin, pdata, data->name))
319 WLog_DBG(TAG,
"intercepting channel '%s'", data->name);
320 auto cdata = dump_get_plugin_data(plugin, pdata);
323 WLog_ERR(TAG,
"Missing channel data");
327 if (!cdata->ensure_path_exists())
330 auto stream = cdata->stream(data->name, data->isBackData);
331 auto buffer =
reinterpret_cast<const char*
>(Stream_ConstBuffer(data->data));
332 if (!stream.is_open() || !stream.good())
334 WLog_ERR(TAG,
"Could not write to stream");
337 const auto s = Stream_Length(data->data);
338 if (s > std::numeric_limits<std::streamsize>::max())
340 WLog_ERR(TAG,
"Stream length %" PRIuz
" exceeds std::streamsize::max", s);
343 stream.write(buffer,
static_cast<std::streamsize
>(s));
346 WLog_ERR(TAG,
"Could not write to stream");
355static std::vector<std::string> split(
const std::string& input,
const std::string& regex)
358 std::regex re(regex);
359 std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
360 std::sregex_token_iterator last;
361 return { first, last };
364static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata,
void* )
366 WINPR_ASSERT(plugin);
369 auto custom = dump_get_plugin_data(plugin);
370 WINPR_ASSERT(custom);
372 auto config = pdata->config;
373 WINPR_ASSERT(config);
378 WLog_ERR(TAG,
"Missing configuration entry [%s/%s], can not continue", plugin_name,
382 auto cchannels =
pf_config_get(config, plugin_name, key_channels);
385 WLog_ERR(TAG,
"Missing configuration entry [%s/%s], can not continue", plugin_name,
390 std::string path(cpath);
391 std::string channels(cchannels);
392 std::vector<std::string> list = split(channels,
"[;,]");
393 auto cfg =
new ChannelData(path, std::move(list), custom->session());
394 if (!cfg || !cfg->create())
400 dump_set_plugin_data(plugin, pdata, cfg);
402 WLog_DBG(TAG,
"starting session dump %" PRIu64, cfg->session());
406static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata,
void* )
408 WINPR_ASSERT(plugin);
411 auto cfg = dump_get_plugin_data(plugin, pdata);
413 WLog_DBG(TAG,
"ending session dump %" PRIu64, cfg->session());
414 dump_set_plugin_data(plugin, pdata,
nullptr);
418static BOOL dump_unload(proxyPlugin* plugin)
422 delete static_cast<PluginData*
>(plugin->custom);
426extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
429BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata)
431 proxyPlugin plugin = {};
433 plugin.name = plugin_name;
434 plugin.description = plugin_desc;
436 plugin.PluginUnload = dump_unload;
437 plugin.ServerSessionStarted = dump_session_started;
438 plugin.ServerSessionEnd = dump_session_end;
440 plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
441 plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
442 plugin.DynChannelIntercept = dump_dyn_channel_intercept;
444 plugin.custom =
new PluginData(plugins_manager);
447 plugin.userdata = userdata;
449 return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
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