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
47static 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
55static 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
123static 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
138static 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
155static 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
172static 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
187static 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
209static 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
248static 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
260static BOOL filter_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, DynChannelState* data)
261{
262 WINPR_ASSERT(plugin);
263 WINPR_ASSERT(pdata);
264
265 auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
266 WINPR_ASSERT(mgr);
267
268 WINPR_ASSERT(mgr->SetPluginData);
269 return mgr->SetPluginData(mgr, plugin_name, pdata, data);
270}
271
272#if defined(REPLY_WITH_EMPTY_OFFER)
273static UINT8 drdynvc_value_to_cblen(UINT32 value)
274{
275 if (value <= 0xFF)
276 return 0;
277 if (value <= 0xFFFF)
278 return 1;
279 return 2;
280}
281
282static BOOL drdynvc_write_variable_uint(wStream* s, UINT32 value, UINT8 cbLen)
283{
284 switch (cbLen)
285 {
286 case 0:
287 Stream_Write_UINT8(s, static_cast<UINT8>(value));
288 break;
289
290 case 1:
291 Stream_Write_UINT16(s, static_cast<UINT16>(value));
292 break;
293
294 default:
295 Stream_Write_UINT32(s, value);
296 break;
297 }
298
299 return TRUE;
300}
301
302static BOOL drdynvc_write_header(wStream* s, UINT32 channelId)
303{
304 const UINT8 cbChId = drdynvc_value_to_cblen(channelId);
305 const UINT8 value = (DATA_PDU << 4) | cbChId;
306 const size_t len = drdynvc_cblen_to_bytes(cbChId) + 1;
307
308 if (!Stream_EnsureRemainingCapacity(s, len))
309 return FALSE;
310
311 Stream_Write_UINT8(s, value);
312 return drdynvc_write_variable_uint(s, value, cbChId);
313}
314
315static BOOL filter_forward_empty_offer(const char* sessionID, proxyDynChannelInterceptData* data,
316 size_t startPosition, UINT32 channelId)
317{
318 WINPR_ASSERT(data);
319
320 Stream_SetPosition(data->data, startPosition);
321 if (!drdynvc_write_header(data->data, channelId))
322 return FALSE;
323
324 if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16)))
325 return FALSE;
326 Stream_Write_UINT16(data->data, 0);
327 Stream_SealLength(data->data);
328
329 WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name,
330 rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER));
331 data->rewritten = TRUE;
332 return TRUE;
333}
334#endif
335
336static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
337{
338 auto data = static_cast<proxyDynChannelInterceptData*>(arg);
339
340 WINPR_ASSERT(plugin);
341 WINPR_ASSERT(pdata);
342 WINPR_ASSERT(data);
343
344 data->result = PF_CHANNEL_RESULT_PASS;
345 if (!data->isBackData &&
346 (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0))
347 {
348 auto state = filter_get_plugin_data(plugin, pdata);
349 if (!state)
350 {
351 WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id,
352 plugin_name);
353 return FALSE;
354 }
355 const size_t inputDataLength = Stream_Length(data->data);
356 UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000;
357
358 const auto pos = Stream_GetPosition(data->data);
359 if (!state->skip())
360 {
361 if (data->first)
362 {
363 uint32_t channelId = 0;
364 size_t length = 0;
365 if (drdynvc_try_read_header(data->data, channelId, length))
366 {
367 if (Stream_GetRemainingLength(data->data) >= 2)
368 {
369 Stream_Read_UINT16(data->data, cmdId);
370 state->setSkipSize(length);
371 state->setDrop(false);
372 }
373 }
374
375 switch (cmdId)
376 {
377 case RDPGFX_CMDID_CACHEIMPORTOFFER:
378 state->setDrop(true);
379 state->setChannelId(channelId);
380 break;
381 default:
382 break;
383 }
384 Stream_SetPosition(data->data, pos);
385 }
386 }
387
388 if (state->skip())
389 {
390 if (!state->skip(inputDataLength))
391 return FALSE;
392
393 if (state->drop())
394 {
395 WLog_WARN(TAG,
396 "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz
397 ", remaining: %" PRIuz "]",
398 pdata->session_id, plugin_name,
399 rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(),
400 inputDataLength, state->remaining());
401 data->result = PF_CHANNEL_RESULT_DROP;
402
403#if defined(REPLY_WITH_EMPTY_OFFER) // TODO: Sending this does screw up some windows RDP server
404 // versions :/
405 if (state->remaining() == 0)
406 {
407 if (!filter_forward_empty_offer(pdata->session_id, data, pos,
408 state->channelId()))
409 return FALSE;
410 }
411#endif
412 }
413 }
414 }
415
416 return TRUE;
417}
418
419static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata, void* /*unused*/)
420{
421 WINPR_ASSERT(plugin);
422 WINPR_ASSERT(pdata);
423
424 auto state = filter_get_plugin_data(plugin, pdata);
425 delete state;
426
427 auto newstate = new DynChannelState();
428 if (!filter_set_plugin_data(plugin, pdata, newstate))
429 {
430 delete newstate;
431 return FALSE;
432 }
433
434 return TRUE;
435}
436
437static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata, void* /*unused*/)
438{
439 WINPR_ASSERT(plugin);
440 WINPR_ASSERT(pdata);
441
442 auto state = filter_get_plugin_data(plugin, pdata);
443 delete state;
444 filter_set_plugin_data(plugin, pdata, nullptr);
445 return TRUE;
446}
447
448#ifdef __cplusplus
449extern "C"
450{
451#endif
452 FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
453#ifdef __cplusplus
454}
455#endif
456
457BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, 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}
FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL param)
Sets a BOOL settings value.