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
60static 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::lock_guard<std::mutex> 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::lock_guard<std::mutex> 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
207static 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
216static 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
231static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, ChannelData* data)
232{
233 WINPR_ASSERT(plugin);
234 WINPR_ASSERT(pdata);
235
236 auto plugindata = dump_get_plugin_data(plugin);
237 WINPR_ASSERT(plugindata);
238
239 auto mgr = plugindata->mgr();
240 WINPR_ASSERT(mgr);
241
242 auto cdata = dump_get_plugin_data(plugin, pdata);
243 delete cdata;
244
245 WINPR_ASSERT(mgr->SetPluginData);
246 return mgr->SetPluginData(mgr, plugin_name, pdata, data);
247}
248
249static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata, const std::string& name)
250{
251 auto config = dump_get_plugin_data(plugin, pdata);
252 if (!config)
253 {
254 WLog_ERR(TAG, "Missing channel data");
255 return false;
256 }
257 return config->dump_enabled(name);
258}
259
260static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
261{
262 auto data = static_cast<proxyChannelToInterceptData*>(arg);
263
264 WINPR_ASSERT(plugin);
265 WINPR_ASSERT(pdata);
266 WINPR_ASSERT(data);
267
268 data->intercept = dump_channel_enabled(plugin, pdata, data->name);
269 if (data->intercept)
270 {
271 auto cdata = dump_get_plugin_data(plugin, pdata);
272 if (!cdata)
273 return FALSE;
274
275 if (!cdata->add(data->name, false))
276 {
277 WLog_ERR(TAG, "failed to create files for '%s'", data->name);
278 }
279 if (!cdata->add(data->name, true))
280 {
281 WLog_ERR(TAG, "failed to create files for '%s'", data->name);
282 }
283 WLog_INFO(TAG, "Dumping channel '%s'", data->name);
284 }
285 return TRUE;
286}
287
288static BOOL dump_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
289 [[maybe_unused]] proxyData* pdata, void* arg)
290{
291 auto data = static_cast<proxyChannelToInterceptData*>(arg);
292
293 WINPR_ASSERT(plugin);
294 WINPR_ASSERT(pdata);
295 WINPR_ASSERT(data);
296
297 auto intercept = std::find(plugin_static_intercept().begin(), plugin_static_intercept().end(),
298 data->name) != plugin_static_intercept().end();
299 if (intercept)
300 {
301 WLog_INFO(TAG, "intercepting channel '%s'", data->name);
302 data->intercept = TRUE;
303 }
304
305 return TRUE;
306}
307
308static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
309{
310 auto data = static_cast<proxyDynChannelInterceptData*>(arg);
311
312 WINPR_ASSERT(plugin);
313 WINPR_ASSERT(pdata);
314 WINPR_ASSERT(data);
315
316 data->result = PF_CHANNEL_RESULT_PASS;
317 if (dump_channel_enabled(plugin, pdata, data->name))
318 {
319 WLog_DBG(TAG, "intercepting channel '%s'", data->name);
320 auto cdata = dump_get_plugin_data(plugin, pdata);
321 if (!cdata)
322 {
323 WLog_ERR(TAG, "Missing channel data");
324 return FALSE;
325 }
326
327 if (!cdata->ensure_path_exists())
328 return FALSE;
329
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())
333 {
334 WLog_ERR(TAG, "Could not write to stream");
335 return FALSE;
336 }
337 const auto s = Stream_Length(data->data);
338 if (s > std::numeric_limits<std::streamsize>::max())
339 {
340 WLog_ERR(TAG, "Stream length %" PRIuz " exceeds std::streamsize::max", s);
341 return FALSE;
342 }
343 stream.write(buffer, static_cast<std::streamsize>(s));
344 if (stream.fail())
345 {
346 WLog_ERR(TAG, "Could not write to stream");
347 return FALSE;
348 }
349 stream.flush();
350 }
351
352 return TRUE;
353}
354
355static std::vector<std::string> split(const std::string& input, const std::string& regex)
356{
357 // passing -1 as the submatch index parameter performs splitting
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 };
362}
363
364static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata, void* /*unused*/)
365{
366 WINPR_ASSERT(plugin);
367 WINPR_ASSERT(pdata);
368
369 auto custom = dump_get_plugin_data(plugin);
370 WINPR_ASSERT(custom);
371
372 auto config = pdata->config;
373 WINPR_ASSERT(config);
374
375 auto cpath = pf_config_get(config, plugin_name, key_path);
376 if (!cpath)
377 {
378 WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
379 key_path);
380 return FALSE;
381 }
382 auto cchannels = pf_config_get(config, plugin_name, key_channels);
383 if (!cchannels)
384 {
385 WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
386 key_channels);
387 return FALSE;
388 }
389
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())
395 {
396 delete cfg;
397 return FALSE;
398 }
399
400 dump_set_plugin_data(plugin, pdata, cfg);
401
402 WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session());
403 return TRUE;
404}
405
406static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void* /*unused*/)
407{
408 WINPR_ASSERT(plugin);
409 WINPR_ASSERT(pdata);
410
411 auto cfg = dump_get_plugin_data(plugin, pdata);
412 if (cfg)
413 WLog_DBG(TAG, "ending session dump %" PRIu64, cfg->session());
414 dump_set_plugin_data(plugin, pdata, nullptr);
415 return TRUE;
416}
417
418static BOOL dump_unload(proxyPlugin* plugin)
419{
420 if (!plugin)
421 return TRUE;
422 delete static_cast<PluginData*>(plugin->custom);
423 return TRUE;
424}
425
426extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
427 void* userdata);
428
429BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
430{
431 proxyPlugin plugin = {};
432
433 plugin.name = plugin_name;
434 plugin.description = plugin_desc;
435
436 plugin.PluginUnload = dump_unload;
437 plugin.ServerSessionStarted = dump_session_started;
438 plugin.ServerSessionEnd = dump_session_end;
439
440 plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
441 plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
442 plugin.DynChannelIntercept = dump_dyn_channel_intercept;
443
444 plugin.custom = new PluginData(plugins_manager);
445 if (!plugin.custom)
446 return FALSE;
447 plugin.userdata = userdata;
448
449 return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
450}
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:1305