FreeRDP
pf_modules.c
1 
23 #include <winpr/assert.h>
24 
25 #include <winpr/file.h>
26 #include <winpr/wlog.h>
27 #include <winpr/path.h>
28 #include <winpr/library.h>
29 #include <freerdp/api.h>
30 #include <freerdp/build-config.h>
31 
32 #include <freerdp/server/proxy/proxy_log.h>
33 #include <freerdp/server/proxy/proxy_modules_api.h>
34 
35 #include <freerdp/server/proxy/proxy_context.h>
36 #include "proxy_modules.h"
37 
38 #define TAG PROXY_TAG("modules")
39 
40 #define MODULE_ENTRY_POINT "proxy_module_entry_point"
41 
42 struct proxy_module
43 {
44  proxyPluginsManager mgr;
45  wArrayList* plugins;
46  wArrayList* handles;
47 };
48 
49 static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result)
50 {
51  switch (result)
52  {
53  case FILTER_TYPE_KEYBOARD:
54  return "FILTER_TYPE_KEYBOARD";
55  case FILTER_TYPE_UNICODE:
56  return "FILTER_TYPE_UNICODE";
57  case FILTER_TYPE_MOUSE:
58  return "FILTER_TYPE_MOUSE";
59  case FILTER_TYPE_MOUSE_EX:
60  return "FILTER_TYPE_MOUSE_EX";
61  case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
62  return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA";
63  case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
64  return "FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA";
65  case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
66  return "FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE";
67  case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
68  return "FILTER_TYPE_SERVER_FETCH_TARGET_ADDR";
69  case FILTER_TYPE_SERVER_PEER_LOGON:
70  return "FILTER_TYPE_SERVER_PEER_LOGON";
71  case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
72  return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE";
73  case FILTER_LAST:
74  return "FILTER_LAST";
75  default:
76  return "FILTER_UNKNOWN";
77  }
78 }
79 
80 static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result)
81 {
82  switch (result)
83  {
84  case HOOK_TYPE_CLIENT_INIT_CONNECT:
85  return "HOOK_TYPE_CLIENT_INIT_CONNECT";
86  case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
87  return "HOOK_TYPE_CLIENT_UNINIT_CONNECT";
88  case HOOK_TYPE_CLIENT_PRE_CONNECT:
89  return "HOOK_TYPE_CLIENT_PRE_CONNECT";
90  case HOOK_TYPE_CLIENT_POST_CONNECT:
91  return "HOOK_TYPE_CLIENT_POST_CONNECT";
92  case HOOK_TYPE_CLIENT_POST_DISCONNECT:
93  return "HOOK_TYPE_CLIENT_POST_DISCONNECT";
94  case HOOK_TYPE_CLIENT_REDIRECT:
95  return "HOOK_TYPE_CLIENT_REDIRECT";
96  case HOOK_TYPE_CLIENT_VERIFY_X509:
97  return "HOOK_TYPE_CLIENT_VERIFY_X509";
98  case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
99  return "HOOK_TYPE_CLIENT_LOGIN_FAILURE";
100  case HOOK_TYPE_CLIENT_END_PAINT:
101  return "HOOK_TYPE_CLIENT_END_PAINT";
102  case HOOK_TYPE_SERVER_POST_CONNECT:
103  return "HOOK_TYPE_SERVER_POST_CONNECT";
104  case HOOK_TYPE_SERVER_ACTIVATE:
105  return "HOOK_TYPE_SERVER_ACTIVATE";
106  case HOOK_TYPE_SERVER_CHANNELS_INIT:
107  return "HOOK_TYPE_SERVER_CHANNELS_INIT";
108  case HOOK_TYPE_SERVER_CHANNELS_FREE:
109  return "HOOK_TYPE_SERVER_CHANNELS_FREE";
110  case HOOK_TYPE_SERVER_SESSION_END:
111  return "HOOK_TYPE_SERVER_SESSION_END";
112  case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
113  return "HOOK_TYPE_CLIENT_LOAD_CHANNELS";
114  case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
115  return "HOOK_TYPE_SERVER_SESSION_INITIALIZE";
116  case HOOK_TYPE_SERVER_SESSION_STARTED:
117  return "HOOK_TYPE_SERVER_SESSION_STARTED";
118  case HOOK_LAST:
119  return "HOOK_LAST";
120  default:
121  return "HOOK_TYPE_UNKNOWN";
122  }
123 }
124 
125 static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
126 {
127  proxyPlugin* plugin = (proxyPlugin*)data;
128  BOOL ok = FALSE;
129 
130  WINPR_UNUSED(index);
131 
132  PF_HOOK_TYPE type = va_arg(ap, PF_HOOK_TYPE);
133  proxyData* pdata = va_arg(ap, proxyData*);
134  void* custom = va_arg(ap, void*);
135 
136  WLog_VRB(TAG, "running hook %s.%s", plugin->name, pf_modules_get_hook_type_string(type));
137 
138  switch (type)
139  {
140  case HOOK_TYPE_CLIENT_INIT_CONNECT:
141  ok = IFCALLRESULT(TRUE, plugin->ClientInitConnect, plugin, pdata, custom);
142  break;
143  case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
144  ok = IFCALLRESULT(TRUE, plugin->ClientUninitConnect, plugin, pdata, custom);
145  break;
146  case HOOK_TYPE_CLIENT_PRE_CONNECT:
147  ok = IFCALLRESULT(TRUE, plugin->ClientPreConnect, plugin, pdata, custom);
148  break;
149 
150  case HOOK_TYPE_CLIENT_POST_CONNECT:
151  ok = IFCALLRESULT(TRUE, plugin->ClientPostConnect, plugin, pdata, custom);
152  break;
153 
154  case HOOK_TYPE_CLIENT_REDIRECT:
155  ok = IFCALLRESULT(TRUE, plugin->ClientRedirect, plugin, pdata, custom);
156  break;
157 
158  case HOOK_TYPE_CLIENT_POST_DISCONNECT:
159  ok = IFCALLRESULT(TRUE, plugin->ClientPostDisconnect, plugin, pdata, custom);
160  break;
161 
162  case HOOK_TYPE_CLIENT_VERIFY_X509:
163  ok = IFCALLRESULT(TRUE, plugin->ClientX509Certificate, plugin, pdata, custom);
164  break;
165 
166  case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
167  ok = IFCALLRESULT(TRUE, plugin->ClientLoginFailure, plugin, pdata, custom);
168  break;
169 
170  case HOOK_TYPE_CLIENT_END_PAINT:
171  ok = IFCALLRESULT(TRUE, plugin->ClientEndPaint, plugin, pdata, custom);
172  break;
173 
174  case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
175  ok = IFCALLRESULT(TRUE, plugin->ClientLoadChannels, plugin, pdata, custom);
176  break;
177 
178  case HOOK_TYPE_SERVER_POST_CONNECT:
179  ok = IFCALLRESULT(TRUE, plugin->ServerPostConnect, plugin, pdata, custom);
180  break;
181 
182  case HOOK_TYPE_SERVER_ACTIVATE:
183  ok = IFCALLRESULT(TRUE, plugin->ServerPeerActivate, plugin, pdata, custom);
184  break;
185 
186  case HOOK_TYPE_SERVER_CHANNELS_INIT:
187  ok = IFCALLRESULT(TRUE, plugin->ServerChannelsInit, plugin, pdata, custom);
188  break;
189 
190  case HOOK_TYPE_SERVER_CHANNELS_FREE:
191  ok = IFCALLRESULT(TRUE, plugin->ServerChannelsFree, plugin, pdata, custom);
192  break;
193 
194  case HOOK_TYPE_SERVER_SESSION_END:
195  ok = IFCALLRESULT(TRUE, plugin->ServerSessionEnd, plugin, pdata, custom);
196  break;
197 
198  case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
199  ok = IFCALLRESULT(TRUE, plugin->ServerSessionInitialize, plugin, pdata, custom);
200  break;
201 
202  case HOOK_TYPE_SERVER_SESSION_STARTED:
203  ok = IFCALLRESULT(TRUE, plugin->ServerSessionStarted, plugin, pdata, custom);
204  break;
205 
206  case HOOK_LAST:
207  default:
208  WLog_ERR(TAG, "invalid hook called");
209  }
210 
211  if (!ok)
212  {
213  WLog_INFO(TAG, "plugin %s, hook %s failed!", plugin->name,
214  pf_modules_get_hook_type_string(type));
215  return FALSE;
216  }
217  return TRUE;
218 }
219 
220 /*
221  * runs all hooks of type `type`.
222  *
223  * @type: hook type to run.
224  * @server: pointer of server's rdpContext struct of the current session.
225  */
226 BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata, void* custom)
227 {
228  WINPR_ASSERT(module);
229  WINPR_ASSERT(module->plugins);
230  return ArrayList_ForEach(module->plugins, pf_modules_proxy_ArrayList_ForEachFkt, type, pdata,
231  custom);
232 }
233 
234 static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
235 {
236  proxyPlugin* plugin = (proxyPlugin*)data;
237  BOOL result = FALSE;
238 
239  WINPR_UNUSED(index);
240 
241  PF_FILTER_TYPE type = va_arg(ap, PF_FILTER_TYPE);
242  proxyData* pdata = va_arg(ap, proxyData*);
243  void* param = va_arg(ap, void*);
244 
245  WLog_VRB(TAG, "running filter: %s", plugin->name);
246 
247  switch (type)
248  {
249  case FILTER_TYPE_KEYBOARD:
250  result = IFCALLRESULT(TRUE, plugin->KeyboardEvent, plugin, pdata, param);
251  break;
252 
253  case FILTER_TYPE_UNICODE:
254  result = IFCALLRESULT(TRUE, plugin->UnicodeEvent, plugin, pdata, param);
255  break;
256 
257  case FILTER_TYPE_MOUSE:
258  result = IFCALLRESULT(TRUE, plugin->MouseEvent, plugin, pdata, param);
259  break;
260 
261  case FILTER_TYPE_MOUSE_EX:
262  result = IFCALLRESULT(TRUE, plugin->MouseExEvent, plugin, pdata, param);
263  break;
264 
265  case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
266  result = IFCALLRESULT(TRUE, plugin->ClientChannelData, plugin, pdata, param);
267  break;
268 
269  case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
270  result = IFCALLRESULT(TRUE, plugin->ServerChannelData, plugin, pdata, param);
271  break;
272 
273  case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
274  result = IFCALLRESULT(TRUE, plugin->ChannelCreate, plugin, pdata, param);
275  break;
276 
277  case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
278  result = IFCALLRESULT(TRUE, plugin->DynamicChannelCreate, plugin, pdata, param);
279  break;
280 
281  case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
282  result = IFCALLRESULT(TRUE, plugin->ServerFetchTargetAddr, plugin, pdata, param);
283  break;
284 
285  case FILTER_TYPE_SERVER_PEER_LOGON:
286  result = IFCALLRESULT(TRUE, plugin->ServerPeerLogon, plugin, pdata, param);
287  break;
288 
289  case FILTER_TYPE_INTERCEPT_CHANNEL:
290  result = IFCALLRESULT(TRUE, plugin->DynChannelIntercept, plugin, pdata, param);
291  break;
292 
293  case FILTER_TYPE_DYN_INTERCEPT_LIST:
294  result = IFCALLRESULT(TRUE, plugin->DynChannelToIntercept, plugin, pdata, param);
295  break;
296 
297  case FILTER_TYPE_STATIC_INTERCEPT_LIST:
298  result = IFCALLRESULT(TRUE, plugin->StaticChannelToIntercept, plugin, pdata, param);
299  break;
300 
301  case FILTER_LAST:
302  default:
303  WLog_ERR(TAG, "invalid filter called");
304  }
305 
306  if (!result)
307  {
308  /* current filter return FALSE, no need to run other filters. */
309  WLog_DBG(TAG, "plugin %s, filter type [%s] returned FALSE", plugin->name,
310  pf_modules_get_filter_type_string(type));
311  }
312  return result;
313 }
314 
315 /*
316  * runs all filters of type `type`.
317  *
318  * @type: filter type to run.
319  * @server: pointer of server's rdpContext struct of the current session.
320  */
321 BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata, void* param)
322 {
323  WINPR_ASSERT(module);
324  WINPR_ASSERT(module->plugins);
325 
326  return ArrayList_ForEach(module->plugins, pf_modules_ArrayList_ForEachFkt, type, pdata, param);
327 }
328 
329 /*
330  * stores per-session data needed by a plugin.
331  *
332  * @context: current session server's rdpContext instance.
333  * @info: pointer to per-session data.
334  */
335 static BOOL pf_modules_set_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
336  proxyData* pdata, void* data)
337 {
338  union
339  {
340  const char* ccp;
341  char* cp;
342  } ccharconv;
343 
344  WINPR_ASSERT(plugin_name);
345 
346  ccharconv.ccp = plugin_name;
347  if (data == NULL) /* no need to store anything */
348  return FALSE;
349 
350  if (!HashTable_Insert(pdata->modules_info, ccharconv.cp, data))
351  {
352  WLog_ERR(TAG, "[%s]: HashTable_Insert failed!");
353  return FALSE;
354  }
355 
356  return TRUE;
357 }
358 
359 /*
360  * returns per-session data needed a plugin.
361  *
362  * @context: current session server's rdpContext instance.
363  * if there's no data related to `plugin_name` in `context` (current session), a NULL will be
364  * returned.
365  */
366 static void* pf_modules_get_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
367  proxyData* pdata)
368 {
369  union
370  {
371  const char* ccp;
372  char* cp;
373  } ccharconv;
374  WINPR_ASSERT(plugin_name);
375  WINPR_ASSERT(pdata);
376  ccharconv.ccp = plugin_name;
377 
378  return HashTable_GetItemValue(pdata->modules_info, ccharconv.cp);
379 }
380 
381 static void pf_modules_abort_connect(proxyPluginsManager* mgr, proxyData* pdata)
382 {
383  WINPR_ASSERT(pdata);
384  WLog_DBG(TAG, "is called!");
385  proxy_data_abort_connect(pdata);
386 }
387 
388 static BOOL pf_modules_register_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
389 {
390  proxyPlugin* plugin = (proxyPlugin*)data;
391  proxyPlugin* plugin_to_register = va_arg(ap, proxyPlugin*);
392 
393  WINPR_UNUSED(index);
394 
395  if (strcmp(plugin->name, plugin_to_register->name) == 0)
396  {
397  WLog_ERR(TAG, "can not register plugin '%s', it is already registered!", plugin->name);
398  return FALSE;
399  }
400  return TRUE;
401 }
402 
403 static BOOL pf_modules_register_plugin(proxyPluginsManager* mgr,
404  const proxyPlugin* plugin_to_register)
405 {
406  proxyPlugin internal = { 0 };
407  proxyModule* module = (proxyModule*)mgr;
408  WINPR_ASSERT(module);
409 
410  if (!plugin_to_register)
411  return FALSE;
412 
413  internal = *plugin_to_register;
414  internal.mgr = mgr;
415 
416  /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */
417  if (!ArrayList_ForEach(module->plugins, pf_modules_register_ArrayList_ForEachFkt, &internal))
418  return FALSE;
419 
420  if (!ArrayList_Append(module->plugins, &internal))
421  {
422  WLog_ERR(TAG, "failed adding plugin to list: %s", plugin_to_register->name);
423  return FALSE;
424  }
425 
426  return TRUE;
427 }
428 
429 static BOOL pf_modules_load_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
430 {
431  proxyPlugin* plugin = (proxyPlugin*)data;
432  const char* plugin_name = va_arg(ap, const char*);
433  BOOL* res = va_arg(ap, BOOL*);
434 
435  WINPR_UNUSED(index);
436  WINPR_UNUSED(ap);
437  WINPR_ASSERT(res);
438 
439  if (strcmp(plugin->name, plugin_name) == 0)
440  *res = TRUE;
441  return TRUE;
442 }
443 
444 BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name)
445 {
446  BOOL rc = FALSE;
447  WINPR_ASSERT(module);
448  if (ArrayList_Count(module->plugins) < 1)
449  return FALSE;
450  if (!ArrayList_ForEach(module->plugins, pf_modules_load_ArrayList_ForEachFkt, plugin_name, &rc))
451  return FALSE;
452  return rc;
453 }
454 
455 static BOOL pf_modules_print_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
456 {
457  proxyPlugin* plugin = (proxyPlugin*)data;
458 
459  WINPR_UNUSED(index);
460  WINPR_UNUSED(ap);
461 
462  WLog_INFO(TAG, "\tName: %s", plugin->name);
463  WLog_INFO(TAG, "\tDescription: %s", plugin->description);
464  return TRUE;
465 }
466 
467 void pf_modules_list_loaded_plugins(proxyModule* module)
468 {
469  size_t count = 0;
470 
471  WINPR_ASSERT(module);
472  WINPR_ASSERT(module->plugins);
473 
474  count = ArrayList_Count(module->plugins);
475 
476  if (count > 0)
477  WLog_INFO(TAG, "Loaded plugins:");
478 
479  ArrayList_ForEach(module->plugins, pf_modules_print_ArrayList_ForEachFkt);
480 }
481 
482 static BOOL pf_modules_load_module(const char* module_path, proxyModule* module, void* userdata)
483 {
484  WINPR_ASSERT(module);
485 
486  HANDLE handle = LoadLibraryX(module_path);
487 
488  if (handle == NULL)
489  {
490  WLog_ERR(TAG, "failed loading external library: %s", module_path);
491  return FALSE;
492  }
493 
494  proxyModuleEntryPoint pEntryPoint =
495  GetProcAddressAs(handle, MODULE_ENTRY_POINT, proxyModuleEntryPoint);
496  if (!pEntryPoint)
497  {
498  WLog_ERR(TAG, "GetProcAddress failed while loading %s", module_path);
499  goto error;
500  }
501  if (!ArrayList_Append(module->handles, handle))
502  {
503  WLog_ERR(TAG, "ArrayList_Append failed!");
504  goto error;
505  }
506  return pf_modules_add(module, pEntryPoint, userdata);
507 
508 error:
509  FreeLibrary(handle);
510  return FALSE;
511 }
512 
513 static void free_handle(void* obj)
514 {
515  HANDLE handle = (HANDLE)obj;
516  if (handle)
517  FreeLibrary(handle);
518 }
519 
520 static void free_plugin(void* obj)
521 {
522  proxyPlugin* plugin = (proxyPlugin*)obj;
523  WINPR_ASSERT(plugin);
524 
525  if (!IFCALLRESULT(TRUE, plugin->PluginUnload, plugin))
526  WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name);
527 
528  free(plugin);
529 }
530 
531 static void* new_plugin(const void* obj)
532 {
533  const proxyPlugin* src = obj;
534  proxyPlugin* proxy = calloc(1, sizeof(proxyPlugin));
535  if (!proxy)
536  return NULL;
537  *proxy = *src;
538  return proxy;
539 }
540 
541 proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count)
542 {
543  wObject* obj = NULL;
544  char* path = NULL;
545  proxyModule* module = calloc(1, sizeof(proxyModule));
546  if (!module)
547  return NULL;
548 
549  module->mgr.RegisterPlugin = pf_modules_register_plugin;
550  module->mgr.SetPluginData = pf_modules_set_plugin_data;
551  module->mgr.GetPluginData = pf_modules_get_plugin_data;
552  module->mgr.AbortConnect = pf_modules_abort_connect;
553  module->plugins = ArrayList_New(FALSE);
554 
555  if (module->plugins == NULL)
556  {
557  WLog_ERR(TAG, "ArrayList_New failed!");
558  goto error;
559  }
560  obj = ArrayList_Object(module->plugins);
561  WINPR_ASSERT(obj);
562 
563  obj->fnObjectFree = free_plugin;
564  obj->fnObjectNew = new_plugin;
565 
566  module->handles = ArrayList_New(FALSE);
567  if (module->handles == NULL)
568  {
569 
570  WLog_ERR(TAG, "ArrayList_New failed!");
571  goto error;
572  }
573  ArrayList_Object(module->handles)->fnObjectFree = free_handle;
574 
575  if (count > 0)
576  {
577  WINPR_ASSERT(root_dir);
578  if (!winpr_PathFileExists(root_dir))
579  path = GetCombinedPath(FREERDP_INSTALL_PREFIX, root_dir);
580  else
581  path = _strdup(root_dir);
582 
583  if (!winpr_PathFileExists(path))
584  {
585  if (!winpr_PathMakePath(path, NULL))
586  {
587  WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir);
588  goto error;
589  }
590  }
591 
592  if (winpr_PathFileExists(path))
593  WLog_DBG(TAG, "modules root directory: %s", path);
594 
595  for (size_t i = 0; i < count; i++)
596  {
597  char name[8192] = { 0 };
598  char* fullpath = NULL;
599  (void)_snprintf(name, sizeof(name), "proxy-%s-plugin%s", modules[i],
600  FREERDP_SHARED_LIBRARY_SUFFIX);
601  fullpath = GetCombinedPath(path, name);
602  pf_modules_load_module(fullpath, module, NULL);
603  free(fullpath);
604  }
605  }
606 
607  free(path);
608  return module;
609 
610 error:
611  free(path);
612  pf_modules_free(module);
613  return NULL;
614 }
615 
616 void pf_modules_free(proxyModule* module)
617 {
618  if (!module)
619  return;
620 
621  ArrayList_Free(module->plugins);
622  ArrayList_Free(module->handles);
623  free(module);
624 }
625 
626 BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata)
627 {
628  WINPR_ASSERT(module);
629  WINPR_ASSERT(ep);
630 
631  return ep(&module->mgr, userdata);
632 }
This struct contains function pointer to initialize/free objects.
Definition: collections.h:57