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