FreeRDP
Loading...
Searching...
No Matches
bitmap-filter.cpp
1
24#include <iostream>
25#include <vector>
26#include <string>
27#include <algorithm>
28#include <map>
29#include <memory>
30#include <mutex>
31
32#include <freerdp/server/proxy/proxy_modules_api.h>
33#include <freerdp/server/proxy/proxy_context.h>
34
35#include <freerdp/channels/drdynvc.h>
36#include <freerdp/channels/rdpgfx.h>
37#include <freerdp/utils/gfx.h>
38
39#define TAG MODULE_TAG("persist-bitmap-filter")
40
41// #define REPLY_WITH_EMPTY_OFFER
42
43static constexpr char plugin_name[] = "bitmap-filter";
44static constexpr char plugin_desc[] =
45 "this plugin deactivates and filters persistent bitmap cache.";
46
47[[nodiscard]] static const std::vector<std::string>& plugin_static_intercept()
48{
49 static std::vector<std::string> vec;
50 if (vec.empty())
51 vec.emplace_back(DRDYNVC_SVC_CHANNEL_NAME);
52 return vec;
53}
54
55[[nodiscard]] static const std::vector<std::string>& plugin_dyn_intercept()
56{
57 static std::vector<std::string> vec;
58 if (vec.empty())
59 vec.emplace_back(RDPGFX_DVC_CHANNEL_NAME);
60 return vec;
61}
62
63class DynChannelState
64{
65
66 public:
67 [[nodiscard]] bool skip() const
68 {
69 return _toSkip != 0;
70 }
71
72 [[nodiscard]] bool skip(size_t s)
73 {
74 if (s > _toSkip)
75 _toSkip = 0;
76 else
77 _toSkip -= s;
78 return skip();
79 }
80
81 [[nodiscard]] size_t remaining() const
82 {
83 return _toSkip;
84 }
85
86 [[nodiscard]] size_t total() const
87 {
88 return _totalSkipSize;
89 }
90
91 void setSkipSize(size_t len)
92 {
93 _toSkip = _totalSkipSize = len;
94 }
95
96 [[nodiscard]] bool drop() const
97 {
98 return _drop;
99 }
100
101 void setDrop(bool d)
102 {
103 _drop = d;
104 }
105
106 [[nodiscard]] uint32_t channelId() const
107 {
108 return _channelId;
109 }
110
111 void setChannelId(uint32_t id)
112 {
113 _channelId = id;
114 }
115
116 private:
117 size_t _toSkip = 0;
118 size_t _totalSkipSize = 0;
119 bool _drop = false;
120 uint32_t _channelId = 0;
121};
122
123[[nodiscard]] static BOOL filter_client_pre_connect([[maybe_unused]] proxyPlugin* plugin,
124 [[maybe_unused]] proxyData* pdata,
125 [[maybe_unused]] void* custom)
126{
127 WINPR_ASSERT(plugin);
128 WINPR_ASSERT(pdata);
129 WINPR_ASSERT(pdata->pc);
130 WINPR_ASSERT(custom);
131
132 auto settings = pdata->pc->context.settings;
133
134 /* We do not want persistent bitmap cache to be used with proxy */
135 return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, FALSE);
136}
137
138[[nodiscard]] static BOOL filter_dyn_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
139 [[maybe_unused]] proxyData* pdata,
140 [[maybe_unused]] void* arg)
141{
142 auto data = static_cast<proxyChannelToInterceptData*>(arg);
143
144 WINPR_ASSERT(plugin);
145 WINPR_ASSERT(pdata);
146 WINPR_ASSERT(data);
147
148 auto intercept = std::find(plugin_dyn_intercept().begin(), plugin_dyn_intercept().end(),
149 data->name) != plugin_dyn_intercept().end();
150 if (intercept)
151 data->intercept = TRUE;
152 return TRUE;
153}
154
155[[nodiscard]] static BOOL filter_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
156 [[maybe_unused]] proxyData* pdata,
157 [[maybe_unused]] void* arg)
158{
159 auto data = static_cast<proxyChannelToInterceptData*>(arg);
160
161 WINPR_ASSERT(plugin);
162 WINPR_ASSERT(pdata);
163 WINPR_ASSERT(data);
164
165 auto intercept = std::find(plugin_static_intercept().begin(), plugin_static_intercept().end(),
166 data->name) != plugin_static_intercept().end();
167 if (intercept)
168 data->intercept = TRUE;
169 return TRUE;
170}
171
172[[nodiscard]] static size_t drdynvc_cblen_to_bytes(UINT8 cbLen)
173{
174 switch (cbLen)
175 {
176 case 0:
177 return 1;
178
179 case 1:
180 return 2;
181
182 default:
183 return 4;
184 }
185}
186
187[[nodiscard]] static UINT32 drdynvc_read_variable_uint(wStream* s, UINT8 cbLen)
188{
189 UINT32 val = 0;
190
191 switch (cbLen)
192 {
193 case 0:
194 Stream_Read_UINT8(s, val);
195 break;
196
197 case 1:
198 Stream_Read_UINT16(s, val);
199 break;
200
201 default:
202 Stream_Read_UINT32(s, val);
203 break;
204 }
205
206 return val;
207}
208
209[[nodiscard]] static BOOL drdynvc_try_read_header(wStream* s, uint32_t& channelId, size_t& length)
210{
211 UINT8 value = 0;
212 Stream_ResetPosition(s);
213 if (Stream_GetRemainingLength(s) < 1)
214 return FALSE;
215 Stream_Read_UINT8(s, value);
216
217 const UINT8 cmd = (value & 0xf0) >> 4;
218 const UINT8 Sp = (value & 0x0c) >> 2;
219 const UINT8 cbChId = (value & 0x03);
220
221 switch (cmd)
222 {
223 case DATA_PDU:
224 case DATA_FIRST_PDU:
225 break;
226 default:
227 return FALSE;
228 }
229
230 const size_t channelIdLen = drdynvc_cblen_to_bytes(cbChId);
231 if (Stream_GetRemainingLength(s) < channelIdLen)
232 return FALSE;
233
234 channelId = drdynvc_read_variable_uint(s, cbChId);
235 length = Stream_Length(s);
236 if (cmd == DATA_FIRST_PDU)
237 {
238 const size_t dataLen = drdynvc_cblen_to_bytes(Sp);
239 if (Stream_GetRemainingLength(s) < dataLen)
240 return FALSE;
241
242 length = drdynvc_read_variable_uint(s, Sp);
243 }
244
245 return TRUE;
246}
247
248[[nodiscard]] static DynChannelState* filter_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
249{
250 WINPR_ASSERT(plugin);
251 WINPR_ASSERT(pdata);
252
253 auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
254 WINPR_ASSERT(mgr);
255
256 WINPR_ASSERT(mgr->GetPluginData);
257 return static_cast<DynChannelState*>(mgr->GetPluginData(mgr, plugin_name, pdata));
258}
259
260[[nodiscard]] static BOOL filter_set_plugin_data(proxyPlugin* plugin, proxyData* pdata,
261 DynChannelState* data)
262{
263 WINPR_ASSERT(plugin);
264 WINPR_ASSERT(pdata);
265
266 auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
267 WINPR_ASSERT(mgr);
268
269 WINPR_ASSERT(mgr->SetPluginData);
270 return mgr->SetPluginData(mgr, plugin_name, pdata, data);
271}
272
273#if defined(REPLY_WITH_EMPTY_OFFER)
274[[nodiscard]] static UINT8 drdynvc_value_to_cblen(UINT32 value)
275{
276 if (value <= 0xFF)
277 return 0;
278 if (value <= 0xFFFF)
279 return 1;
280 return 2;
281}
282
283[[nodiscard]] static BOOL drdynvc_write_variable_uint(wStream* s, UINT32 value, UINT8 cbLen)
284{
285 switch (cbLen)
286 {
287 case 0:
288 Stream_Write_UINT8(s, static_cast<UINT8>(value));
289 break;
290
291 case 1:
292 Stream_Write_UINT16(s, static_cast<UINT16>(value));
293 break;
294
295 default:
296 Stream_Write_UINT32(s, value);
297 break;
298 }
299
300 return TRUE;
301}
302
303[[nodiscard]] static BOOL drdynvc_write_header(wStream* s, UINT32 channelId)
304{
305 const UINT8 cbChId = drdynvc_value_to_cblen(channelId);
306 const UINT8 value = (DATA_PDU << 4) | cbChId;
307 const size_t len = drdynvc_cblen_to_bytes(cbChId) + 1;
308
309 if (!Stream_EnsureRemainingCapacity(s, len))
310 return FALSE;
311
312 Stream_Write_UINT8(s, value);
313 return drdynvc_write_variable_uint(s, value, cbChId);
314}
315
316[[nodiscard]] static BOOL filter_forward_empty_offer(const char* sessionID,
318 size_t startPosition, UINT32 channelId)
319{
320 WINPR_ASSERT(data);
321
322 if (!Stream_SetPosition(data->data, startPosition))
323 return FALSE;
324 if (!drdynvc_write_header(data->data, channelId))
325 return FALSE;
326
327 if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16)))
328 return FALSE;
329 Stream_Write_UINT16(data->data, 0);
330 Stream_SealLength(data->data);
331
332 WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name,
333 rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER));
334 data->rewritten = TRUE;
335 return TRUE;
336}
337#endif
338
339[[nodiscard]] static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata,
340 void* arg)
341{
342 auto data = static_cast<proxyDynChannelInterceptData*>(arg);
343
344 WINPR_ASSERT(plugin);
345 WINPR_ASSERT(pdata);
346 WINPR_ASSERT(data);
347
348 data->result = PF_CHANNEL_RESULT_PASS;
349 if (!data->isBackData &&
350 (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0))
351 {
352 auto state = filter_get_plugin_data(plugin, pdata);
353 if (!state)
354 {
355 WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id,
356 plugin_name);
357 return FALSE;
358 }
359 const size_t inputDataLength = Stream_Length(data->data);
360 UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000;
361
362 const auto pos = Stream_GetPosition(data->data);
363 if (!state->skip())
364 {
365 if (data->first)
366 {
367 uint32_t channelId = 0;
368 size_t length = 0;
369 if (drdynvc_try_read_header(data->data, channelId, length))
370 {
371 if (Stream_GetRemainingLength(data->data) >= 2)
372 {
373 Stream_Read_UINT16(data->data, cmdId);
374 state->setSkipSize(length);
375 state->setDrop(false);
376 }
377 }
378
379 switch (cmdId)
380 {
381 case RDPGFX_CMDID_CACHEIMPORTOFFER:
382 state->setDrop(true);
383 state->setChannelId(channelId);
384 break;
385 default:
386 break;
387 }
388 if (!Stream_SetPosition(data->data, pos))
389 return FALSE;
390 }
391 }
392
393 if (state->skip())
394 {
395 if (state->skip(inputDataLength))
396 {
397 WLog_DBG(TAG,
398 "skipping data, but %" PRIuz " bytes left [stream has %" PRIuz " bytes]",
399 state->remaining(), inputDataLength);
400 }
401
402 if (state->drop())
403 {
404 WLog_WARN(TAG,
405 "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz
406 ", remaining: %" PRIuz "]",
407 pdata->session_id, plugin_name,
408 rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(),
409 inputDataLength, state->remaining());
410 data->result = PF_CHANNEL_RESULT_DROP;
411
412#if defined(REPLY_WITH_EMPTY_OFFER) // TODO: Sending this does screw up some windows RDP server
413 // versions :/
414 if (state->remaining() == 0)
415 {
416 if (!filter_forward_empty_offer(pdata->session_id, data, pos,
417 state->channelId()))
418 return FALSE;
419 }
420#endif
421 }
422 }
423 }
424
425 return TRUE;
426}
427
428[[nodiscard]] static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata,
429 void* /*unused*/)
430{
431 WINPR_ASSERT(plugin);
432 WINPR_ASSERT(pdata);
433
434 auto state = filter_get_plugin_data(plugin, pdata);
435 delete state;
436
437 auto newstate = new DynChannelState();
438 if (!filter_set_plugin_data(plugin, pdata, newstate))
439 {
440 delete newstate;
441 return FALSE;
442 }
443
444 return TRUE;
445}
446
447[[nodiscard]] static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata,
448 void* /*unused*/)
449{
450 WINPR_ASSERT(plugin);
451 WINPR_ASSERT(pdata);
452
453 auto state = filter_get_plugin_data(plugin, pdata);
454 delete state;
455 return filter_set_plugin_data(plugin, pdata, nullptr);
456}
457
458[[nodiscard]] static BOOL int_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
459 void* userdata)
460{
461 proxyPlugin plugin = {};
462
463 plugin.name = plugin_name;
464 plugin.description = plugin_desc;
465
466 plugin.ServerSessionStarted = filter_server_session_started;
467 plugin.ServerSessionEnd = filter_server_session_end;
468
469 plugin.ClientPreConnect = filter_client_pre_connect;
470
471 plugin.StaticChannelToIntercept = filter_static_channel_intercept_list;
472 plugin.DynChannelToIntercept = filter_dyn_channel_intercept_list;
473 plugin.DynChannelIntercept = filter_dyn_channel_intercept;
474
475 plugin.custom = plugins_manager;
476 if (!plugin.custom)
477 return FALSE;
478 plugin.userdata = userdata;
479
480 return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
481}
482
483#ifdef __cplusplus
484extern "C"
485{
486#endif
487#if defined(BUILD_SHARED_LIBS)
488 [[nodiscard]]
489 FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
490
491 BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
492 {
493 return int_proxy_module_entry_point(plugins_manager, userdata);
494 }
495#else
496[[nodiscard]]
497FREERDP_API BOOL bitmap_filter_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
498 void* userdata);
499BOOL bitmap_filter_proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
500{
501 return int_proxy_module_entry_point(plugins_manager, userdata);
502}
503#endif
504#ifdef __cplusplus
505}
506#endif
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL val)
Sets a BOOL settings value.