FreeRDP
Loading...
Searching...
No Matches
dyn-channel-dump.cpp
1
24#include <fstream>
25#include <iostream>
26#include <regex>
27#include <limits>
28#include <utility>
29#include <vector>
30#include <sstream>
31#include <string>
32#include <algorithm>
33#include <map>
34#include <memory>
35#include <mutex>
36#include <atomic>
37#if __has_include(<filesystem>)
38#include <filesystem>
39namespace fs = std::filesystem;
40#elif __has_include(<experimental/filesystem>)
41#include <experimental/filesystem>
42namespace fs = std::experimental::filesystem;
43#else
44#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
45#endif
46
47#include <freerdp/server/proxy/proxy_modules_api.h>
48#include <freerdp/server/proxy/proxy_context.h>
49
50#include <freerdp/channels/drdynvc.h>
51#include <freerdp/channels/rdpgfx.h>
52#include <freerdp/utils/gfx.h>
53
54#define TAG MODULE_TAG("dyn-channel-dump")
55
56static constexpr char plugin_name[] = "dyn-channel-dump";
57static constexpr char plugin_desc[] =
58 "This plugin dumps configurable dynamic channel data to a file.";
59
60[[nodiscard]] static const std::vector<std::string>& plugin_static_intercept()
61{
62 static std::vector<std::string> vec;
63 if (vec.empty())
64 vec.emplace_back(DRDYNVC_SVC_CHANNEL_NAME);
65 return vec;
66}
67
68static constexpr char key_path[] = "path";
69static constexpr char key_channels[] = "channels";
70
71class PluginData
72{
73 public:
74 explicit PluginData(proxyPluginsManager* mgr) : _mgr(mgr)
75 {
76 }
77
78 [[nodiscard]] proxyPluginsManager* mgr() const
79 {
80 return _mgr;
81 }
82
83 uint64_t session()
84 {
85 return _sessionid++;
86 }
87
88 private:
89 proxyPluginsManager* _mgr;
90 uint64_t _sessionid{ 0 };
91};
92
93class ChannelData
94{
95 public:
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)
98 {
99 char str[64] = {};
100 (void)_snprintf(str, sizeof(str), "session-%016" PRIx64, _session_id);
101 _base /= str;
102 }
103
104 bool add(const std::string& name, WINPR_ATTR_UNUSED bool back)
105 {
106 std::scoped_lock guard(_mux);
107 if (_map.find(name) == _map.end())
108 {
109 WLog_INFO(TAG, "adding '%s' to dump list", name.c_str());
110 _map.insert({ name, 0 });
111 }
112 return true;
113 }
114
115 std::ofstream stream(const std::string& name, bool back)
116 {
117 std::scoped_lock guard(_mux);
118 auto& atom = _map[name];
119 auto count = atom++;
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);
123 }
124
125 [[nodiscard]] bool dump_enabled(const std::string& name) const
126 {
127 if (name.empty())
128 {
129 WLog_WARN(TAG, "empty dynamic channel name, skipping");
130 return false;
131 }
132
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");
136 return enabled;
137 }
138
139 bool ensure_path_exists()
140 {
141 if (!fs::exists(_base))
142 {
143 if (!fs::create_directories(_base))
144 {
145 WLog_ERR(TAG, "Failed to create dump directory %s", _base.c_str());
146 return false;
147 }
148 }
149 else if (!fs::is_directory(_base))
150 {
151 WLog_ERR(TAG, "dump path %s is not a directory", _base.c_str());
152 return false;
153 }
154 return true;
155 }
156
157 bool create()
158 {
159 if (!ensure_path_exists())
160 return false;
161
162 if (_channels_to_dump.empty())
163 {
164 WLog_ERR(TAG, "Empty configuration entry [%s/%s], can not continue", plugin_name,
165 key_channels);
166 return false;
167 }
168 return true;
169 }
170
171 [[nodiscard]] uint64_t session() const
172 {
173 return _session_id;
174 }
175
176 private:
177 [[nodiscard]] fs::path filepath(const std::string& channel, bool back, uint64_t count) const
178 {
179 auto name = idstr(channel, back);
180 char cstr[32] = {};
181 (void)_snprintf(cstr, sizeof(cstr), "%016" PRIx64 "-", count);
182 auto path = _base / cstr;
183 path += name;
184 path += ".dump";
185 return path;
186 }
187
188 [[nodiscard]] static std::string idstr(const std::string& name, bool back)
189 {
190 std::stringstream ss;
191 ss << name << ".";
192 if (back)
193 ss << "back";
194 else
195 ss << "front";
196 return ss.str();
197 }
198
199 fs::path _base;
200 std::vector<std::string> _channels_to_dump;
201
202 std::mutex _mux;
203 std::map<std::string, uint64_t> _map;
204 uint64_t _session_id;
205};
206
207[[nodiscard]] static PluginData* dump_get_plugin_data(proxyPlugin* plugin)
208{
209 WINPR_ASSERT(plugin);
210
211 auto plugindata = static_cast<PluginData*>(plugin->custom);
212 WINPR_ASSERT(plugindata);
213 return plugindata;
214}
215
216[[nodiscard]] static ChannelData* dump_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
217{
218 WINPR_ASSERT(plugin);
219 WINPR_ASSERT(pdata);
220
221 auto plugindata = dump_get_plugin_data(plugin);
222 WINPR_ASSERT(plugindata);
223
224 auto mgr = plugindata->mgr();
225 WINPR_ASSERT(mgr);
226
227 WINPR_ASSERT(mgr->GetPluginData);
228 return static_cast<ChannelData*>(mgr->GetPluginData(mgr, plugin_name, pdata));
229}
230
231[[nodiscard]] static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata,
232 ChannelData* data)
233{
234 WINPR_ASSERT(plugin);
235 WINPR_ASSERT(pdata);
236
237 auto plugindata = dump_get_plugin_data(plugin);
238 WINPR_ASSERT(plugindata);
239
240 auto mgr = plugindata->mgr();
241 WINPR_ASSERT(mgr);
242
243 auto cdata = dump_get_plugin_data(plugin, pdata);
244 delete cdata;
245
246 WINPR_ASSERT(mgr->SetPluginData);
247 return mgr->SetPluginData(mgr, plugin_name, pdata, data);
248}
249
250[[nodiscard]] static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata,
251 const std::string& name)
252{
253 auto config = dump_get_plugin_data(plugin, pdata);
254 if (!config)
255 {
256 WLog_ERR(TAG, "Missing channel data");
257 return false;
258 }
259 return config->dump_enabled(name);
260}
261
262[[nodiscard]] static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata,
263 void* arg)
264{
265 auto data = static_cast<proxyChannelToInterceptData*>(arg);
266
267 WINPR_ASSERT(plugin);
268 WINPR_ASSERT(pdata);
269 WINPR_ASSERT(data);
270
271 data->intercept = dump_channel_enabled(plugin, pdata, data->name);
272 if (data->intercept)
273 {
274 auto cdata = dump_get_plugin_data(plugin, pdata);
275 if (!cdata)
276 return FALSE;
277
278 if (!cdata->add(data->name, false))
279 {
280 WLog_ERR(TAG, "failed to create files for '%s'", data->name);
281 }
282 if (!cdata->add(data->name, true))
283 {
284 WLog_ERR(TAG, "failed to create files for '%s'", data->name);
285 }
286 WLog_INFO(TAG, "Dumping channel '%s'", data->name);
287 }
288 return TRUE;
289}
290
291[[nodiscard]] static BOOL dump_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
292 [[maybe_unused]] proxyData* pdata,
293 void* arg)
294{
295 auto data = static_cast<proxyChannelToInterceptData*>(arg);
296
297 WINPR_ASSERT(plugin);
298 WINPR_ASSERT(pdata);
299 WINPR_ASSERT(data);
300
301 auto intercept = std::find(plugin_static_intercept().begin(), plugin_static_intercept().end(),
302 data->name) != plugin_static_intercept().end();
303 if (intercept)
304 {
305 WLog_INFO(TAG, "intercepting channel '%s'", data->name);
306 data->intercept = TRUE;
307 }
308
309 return TRUE;
310}
311
312[[nodiscard]] static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata,
313 void* arg)
314{
315 auto data = static_cast<proxyDynChannelInterceptData*>(arg);
316
317 WINPR_ASSERT(plugin);
318 WINPR_ASSERT(pdata);
319 WINPR_ASSERT(data);
320
321 data->result = PF_CHANNEL_RESULT_PASS;
322 if (dump_channel_enabled(plugin, pdata, data->name))
323 {
324 WLog_DBG(TAG, "intercepting channel '%s'", data->name);
325 auto cdata = dump_get_plugin_data(plugin, pdata);
326 if (!cdata)
327 {
328 WLog_ERR(TAG, "Missing channel data");
329 return FALSE;
330 }
331
332 if (!cdata->ensure_path_exists())
333 return FALSE;
334
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())
338 {
339 WLog_ERR(TAG, "Could not write to stream");
340 return FALSE;
341 }
342 const auto s = Stream_Length(data->data);
343 if (s > std::numeric_limits<std::streamsize>::max())
344 {
345 WLog_ERR(TAG, "Stream length %" PRIuz " exceeds std::streamsize::max", s);
346 return FALSE;
347 }
348 stream.write(buffer, static_cast<std::streamsize>(s));
349 if (stream.fail())
350 {
351 WLog_ERR(TAG, "Could not write to stream");
352 return FALSE;
353 }
354 stream.flush();
355 }
356
357 return TRUE;
358}
359
360[[nodiscard]] static std::vector<std::string> split(const std::string& input,
361 const std::string& regex)
362{
363 // passing -1 as the submatch index parameter performs splitting
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 };
368}
369
370[[nodiscard]] static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata,
371 void* /*unused*/)
372{
373 WINPR_ASSERT(plugin);
374 WINPR_ASSERT(pdata);
375
376 auto custom = dump_get_plugin_data(plugin);
377 WINPR_ASSERT(custom);
378
379 auto config = pdata->config;
380 WINPR_ASSERT(config);
381
382 auto cpath = pf_config_get(config, plugin_name, key_path);
383 if (!cpath)
384 {
385 WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
386 key_path);
387 return FALSE;
388 }
389 auto cchannels = pf_config_get(config, plugin_name, key_channels);
390 if (!cchannels)
391 {
392 WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
393 key_channels);
394 return FALSE;
395 }
396
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())
402 {
403 delete cfg;
404 return FALSE;
405 }
406
407 if (!dump_set_plugin_data(plugin, pdata, cfg))
408 return FALSE;
409
410 WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session());
411 return TRUE;
412}
413
414[[nodiscard]] static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void* /*unused*/)
415{
416 WINPR_ASSERT(plugin);
417 WINPR_ASSERT(pdata);
418
419 auto cfg = dump_get_plugin_data(plugin, pdata);
420 if (cfg)
421 WLog_DBG(TAG, "ending session dump %" PRIu64, cfg->session());
422 return dump_set_plugin_data(plugin, pdata, nullptr);
423}
424
425[[nodiscard]] static BOOL dump_unload(proxyPlugin* plugin)
426{
427 if (!plugin)
428 return TRUE;
429 delete static_cast<PluginData*>(plugin->custom);
430 return TRUE;
431}
432
433[[nodiscard]] static BOOL int_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
434 void* userdata)
435{
436 proxyPlugin plugin = {};
437
438 plugin.name = plugin_name;
439 plugin.description = plugin_desc;
440
441 plugin.PluginUnload = dump_unload;
442 plugin.ServerSessionStarted = dump_session_started;
443 plugin.ServerSessionEnd = dump_session_end;
444
445 plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
446 plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
447 plugin.DynChannelIntercept = dump_dyn_channel_intercept;
448
449 plugin.custom = new PluginData(plugins_manager);
450 if (!plugin.custom)
451 return FALSE;
452 plugin.userdata = userdata;
453
454 return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
455}
456
457#ifdef __cplusplus
458extern "C"
459{
460#endif
461#if defined(BUILD_SHARED_LIBS)
462 [[nodiscard]]
463 FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
464
465 BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
466 {
467 return int_proxy_module_entry_point(plugins_manager, userdata);
468 }
469#else
470[[nodiscard]]
471FREERDP_API BOOL dyn_channel_dump_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
472 void* userdata);
473BOOL dyn_channel_dump_proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
474{
475 return int_proxy_module_entry_point(plugins_manager, userdata);
476}
477#endif
478#ifdef __cplusplus
479}
480#endif
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
Definition pf_config.c:1358