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_SetPosition(s, 0);
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 Stream_SetPosition(data->data, startPosition);
323 if (!drdynvc_write_header(data->data, channelId))
324 return FALSE;
325
326 if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16)))
327 return FALSE;
328 Stream_Write_UINT16(data->data, 0);
329 Stream_SealLength(data->data);
330
331 WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name,
332 rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER));
333 data->rewritten = TRUE;
334 return TRUE;
335}
336#endif
337
338[[nodiscard]] static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata,
339 void* arg)
340{
341 auto data = static_cast<proxyDynChannelInterceptData*>(arg);
342
343 WINPR_ASSERT(plugin);
344 WINPR_ASSERT(pdata);
345 WINPR_ASSERT(data);
346
347 data->result = PF_CHANNEL_RESULT_PASS;
348 if (!data->isBackData &&
349 (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0))
350 {
351 auto state = filter_get_plugin_data(plugin, pdata);
352 if (!state)
353 {
354 WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id,
355 plugin_name);
356 return FALSE;
357 }
358 const size_t inputDataLength = Stream_Length(data->data);
359 UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000;
360
361 const auto pos = Stream_GetPosition(data->data);
362 if (!state->skip())
363 {
364 if (data->first)
365 {
366 uint32_t channelId = 0;
367 size_t length = 0;
368 if (drdynvc_try_read_header(data->data, channelId, length))
369 {
370 if (Stream_GetRemainingLength(data->data) >= 2)
371 {
372 Stream_Read_UINT16(data->data, cmdId);
373 state->setSkipSize(length);
374 state->setDrop(false);
375 }
376 }
377
378 switch (cmdId)
379 {
380 case RDPGFX_CMDID_CACHEIMPORTOFFER:
381 state->setDrop(true);
382 state->setChannelId(channelId);
383 break;
384 default:
385 break;
386 }
387 Stream_SetPosition(data->data, pos);
388 }
389 }
390
391 if (state->skip())
392 {
393 if (state->skip(inputDataLength))
394 {
395 WLog_DBG(TAG,
396 "skipping data, but %" PRIuz " bytes left [stream has %" PRIuz " bytes]",
397 state->remaining(), inputDataLength);
398 }
399
400 if (state->drop())
401 {
402 WLog_WARN(TAG,
403 "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz
404 ", remaining: %" PRIuz "]",
405 pdata->session_id, plugin_name,
406 rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(),
407 inputDataLength, state->remaining());
408 data->result = PF_CHANNEL_RESULT_DROP;
409
410#if defined(REPLY_WITH_EMPTY_OFFER) // TODO: Sending this does screw up some windows RDP server
411 // versions :/
412 if (state->remaining() == 0)
413 {
414 if (!filter_forward_empty_offer(pdata->session_id, data, pos,
415 state->channelId()))
416 return FALSE;
417 }
418#endif
419 }
420 }
421 }
422
423 return TRUE;
424}
425
426[[nodiscard]] static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata,
427 void* /*unused*/)
428{
429 WINPR_ASSERT(plugin);
430 WINPR_ASSERT(pdata);
431
432 auto state = filter_get_plugin_data(plugin, pdata);
433 delete state;
434
435 auto newstate = new DynChannelState();
436 if (!filter_set_plugin_data(plugin, pdata, newstate))
437 {
438 delete newstate;
439 return FALSE;
440 }
441
442 return TRUE;
443}
444
445[[nodiscard]] static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata,
446 void* /*unused*/)
447{
448 WINPR_ASSERT(plugin);
449 WINPR_ASSERT(pdata);
450
451 auto state = filter_get_plugin_data(plugin, pdata);
452 delete state;
453 return filter_set_plugin_data(plugin, pdata, nullptr);
454}
455
456[[nodiscard]] static BOOL int_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
457 void* userdata)
458{
459 proxyPlugin plugin = {};
460
461 plugin.name = plugin_name;
462 plugin.description = plugin_desc;
463
464 plugin.ServerSessionStarted = filter_server_session_started;
465 plugin.ServerSessionEnd = filter_server_session_end;
466
467 plugin.ClientPreConnect = filter_client_pre_connect;
468
469 plugin.StaticChannelToIntercept = filter_static_channel_intercept_list;
470 plugin.DynChannelToIntercept = filter_dyn_channel_intercept_list;
471 plugin.DynChannelIntercept = filter_dyn_channel_intercept;
472
473 plugin.custom = plugins_manager;
474 if (!plugin.custom)
475 return FALSE;
476 plugin.userdata = userdata;
477
478 return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
479}
480
481#ifdef __cplusplus
482extern "C"
483{
484#endif
485#if defined(BUILD_SHARED_LIBS)
486 [[nodiscard]]
487 FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
488
489 BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
490 {
491 return int_proxy_module_entry_point(plugins_manager, userdata);
492 }
493#else
494[[nodiscard]]
495FREERDP_API BOOL bitmap_filter_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
496 void* userdata);
497BOOL bitmap_filter_proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
498{
499 return int_proxy_module_entry_point(plugins_manager, userdata);
500}
501#endif
502#ifdef __cplusplus
503}
504#endif
FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL param)
Sets a BOOL settings value.