37 #if __has_include(<filesystem>)
39 namespace fs = std::filesystem;
40 #elif __has_include(<experimental/filesystem>)
41 #include <experimental/filesystem>
42 namespace 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")
56 static constexpr
char plugin_name[] =
"dyn-channel-dump";
57 static constexpr
char plugin_desc[] =
58 "This plugin dumps configurable dynamic channel data to a file.";
60 static const std::vector<std::string>& plugin_static_intercept()
62 static std::vector<std::string> vec;
64 vec.emplace_back(DRDYNVC_SVC_CHANNEL_NAME);
68 static constexpr
char key_path[] =
"path";
69 static 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,
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;
207 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 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 static 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);
249 static 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);
260 static 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);
288 static BOOL dump_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata,
void* arg)
292 WINPR_ASSERT(plugin);
296 auto intercept = std::find(plugin_static_intercept().begin(), plugin_static_intercept().end(),
297 data->name) != plugin_static_intercept().end();
300 WLog_INFO(TAG,
"intercepting channel '%s'", data->name);
301 data->intercept = TRUE;
307 static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata,
void* arg)
311 WINPR_ASSERT(plugin);
315 data->result = PF_CHANNEL_RESULT_PASS;
316 if (dump_channel_enabled(plugin, pdata, data->name))
318 WLog_DBG(TAG,
"intercepting channel '%s'", data->name);
319 auto cdata = dump_get_plugin_data(plugin, pdata);
322 WLog_ERR(TAG,
"Missing channel data");
326 if (!cdata->ensure_path_exists())
329 auto stream = cdata->stream(data->name, data->isBackData);
330 auto buffer =
reinterpret_cast<const char*
>(Stream_ConstBuffer(data->data));
331 if (!stream.is_open() || !stream.good())
333 WLog_ERR(TAG,
"Could not write to stream");
336 const auto s = Stream_Length(data->data);
337 if (s > std::numeric_limits<std::streamsize>::max())
339 WLog_ERR(TAG,
"Stream length %" PRIuz
" exceeds std::streamsize::max", s);
342 stream.write(buffer,
static_cast<std::streamsize
>(s));
345 WLog_ERR(TAG,
"Could not write to stream");
354 static std::vector<std::string> split(
const std::string& input,
const std::string& regex)
357 std::regex re(regex);
358 std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
359 std::sregex_token_iterator last;
360 return { first, last };
363 static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata,
void* )
365 WINPR_ASSERT(plugin);
368 auto custom = dump_get_plugin_data(plugin);
369 WINPR_ASSERT(custom);
371 auto config = pdata->config;
372 WINPR_ASSERT(config);
377 WLog_ERR(TAG,
"Missing configuration entry [%s/%s], can not continue", plugin_name,
381 auto cchannels =
pf_config_get(config, plugin_name, key_channels);
384 WLog_ERR(TAG,
"Missing configuration entry [%s/%s], can not continue", plugin_name,
389 std::string path(cpath);
390 std::string channels(cchannels);
391 std::vector<std::string> list = split(channels,
"[;,]");
392 auto cfg =
new ChannelData(path, std::move(list), custom->session());
393 if (!cfg || !cfg->create())
399 dump_set_plugin_data(plugin, pdata, cfg);
401 WLog_DBG(TAG,
"starting session dump %" PRIu64, cfg->session());
405 static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata,
void* )
407 WINPR_ASSERT(plugin);
410 auto cfg = dump_get_plugin_data(plugin, pdata);
412 WLog_DBG(TAG,
"ending session dump %" PRIu64, cfg->session());
413 dump_set_plugin_data(plugin, pdata,
nullptr);
417 static BOOL dump_unload(proxyPlugin* plugin)
421 delete static_cast<PluginData*
>(plugin->custom);
425 extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
428 BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata)
430 proxyPlugin plugin = {};
432 plugin.name = plugin_name;
433 plugin.description = plugin_desc;
435 plugin.PluginUnload = dump_unload;
436 plugin.ServerSessionStarted = dump_session_started;
437 plugin.ServerSessionEnd = dump_session_end;
439 plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
440 plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
441 plugin.DynChannelIntercept = dump_dyn_channel_intercept;
443 plugin.custom =
new PluginData(plugins_manager);
446 plugin.userdata = userdata;
448 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