summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
committersss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
commitcc3f33db7a8d3c4ad373e646b199808e01bc5d9b (patch)
treeec09d690c7656ab5f2cc72607e05fb359c24d8b2 /src
added webrdp public code
Diffstat (limited to 'src')
l---------src/.clang-format1
-rw-r--r--src/backend.workspace17
l---------src/core/.clang-format1
-rw-r--r--src/core/CMakeLists.txt167
-rw-r--r--src/core/backend_helpers.c459
-rw-r--r--src/core/backend_helpers.h20
-rw-r--r--src/core/base64_url.h187
-rw-r--r--src/core/cmdline.c122
-rw-r--r--src/core/cmdline.h9
-rw-r--r--src/core/config_file.c480
-rw-r--r--src/core/config_file.h9
-rw-r--r--src/core/core.project221
-rw-r--r--src/core/ctl_task.h18
-rw-r--r--src/core/curl_helpers.c864
-rw-r--r--src/core/curl_helpers.h76
-rw-r--r--src/core/ev_loop.c206
-rw-r--r--src/core/ev_loop.h11
-rw-r--r--src/core/exports.c396
-rw-r--r--src/core/exports.h17
-rw-r--r--src/core/globals.h82
l---------src/core/include/.clang-format1
-rw-r--r--src/core/include/webrdp_api_shared_structures.h222
-rw-r--r--src/core/include/webrdp_api_utils.h43
-rw-r--r--src/core/include/webrdp_core_api.h185
-rw-r--r--src/core/include/webrdp_module_api.h109
-rw-r--r--src/core/json_helpers.c275
-rw-r--r--src/core/json_helpers.h15
-rw-r--r--src/core/log.c164
-rw-r--r--src/core/log.h12
-rw-r--r--src/core/main.c220
-rw-r--r--src/core/remote_control.c435
-rw-r--r--src/core/remote_control.h36
-rw-r--r--src/core/socket_helpers.c143
-rw-r--r--src/core/socket_helpers.h12
-rw-r--r--src/core/task.h59
-rw-r--r--src/core/thread_impl.c387
-rw-r--r--src/core/thread_impl.h23
-rw-r--r--src/core/thread_sync.c75
-rw-r--r--src/core/thread_sync.h23
-rw-r--r--src/core/utilities.c115
-rw-r--r--src/core/utilities.h28
-rw-r--r--src/core/wrdp_thpool.c770
-rw-r--r--src/core/wrdp_thpool.h150
-rw-r--r--src/core/wrdp_thpool_internals.h113
-rw-r--r--src/core/ws_protocol.c1206
-rw-r--r--src/core/ws_protocol.h79
-rw-r--r--src/core/ws_server_internals.h7
-rw-r--r--src/core/ws_session.c636
-rw-r--r--src/core/ws_session.h59
l---------src/rdp/.clang-format1
-rw-r--r--src/rdp/CMakeLists.txt82
l---------src/rdp/include/.clang-format1
-rw-r--r--src/rdp/include/rdp_backend_api.h17
-rw-r--r--src/rdp/rdp.project138
-rw-r--r--src/rdp/rdp_channels.c39
-rw-r--r--src/rdp/rdp_channels.h17
-rw-r--r--src/rdp/rdp_clipboard.c1524
-rw-r--r--src/rdp/rdp_clipboard.h52
-rw-r--r--src/rdp/rdp_display_output.c871
-rw-r--r--src/rdp/rdp_display_output.h17
-rw-r--r--src/rdp/rdp_ft.c129
-rw-r--r--src/rdp/rdp_ft.h17
-rw-r--r--src/rdp/rdp_impl.c637
-rw-r--r--src/rdp/rdp_impl.h127
-rw-r--r--src/rdp/rdp_io.c1492
-rw-r--r--src/rdp/rdp_module.c168
-rw-r--r--src/rdp/rdp_module.h7
-rw-r--r--src/rdp/rdp_png.c89
-rw-r--r--src/rdp/rdp_png.h22
-rw-r--r--src/rdp/rdp_rail.c173
-rw-r--r--src/rdp/rdp_rail.h14
-rw-r--r--src/rdp/rdp_settings.c2278
-rw-r--r--src/rdp/rdp_settings.h15
-rw-r--r--src/rdp/rdp_user_input.c458
-rw-r--r--src/rdp/rdp_user_input.h10
-rw-r--r--src/tools/keygen.c57
76 files changed, 17417 insertions, 0 deletions
diff --git a/src/.clang-format b/src/.clang-format
new file mode 120000
index 0000000..7cab60c
--- /dev/null
+++ b/src/.clang-format
@@ -0,0 +1 @@
+../.clang-format \ No newline at end of file
diff --git a/src/backend.workspace b/src/backend.workspace
new file mode 100644
index 0000000..cc2b815
--- /dev/null
+++ b/src/backend.workspace
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CodeLite_Workspace Name="backend" Database="" Version="10.0.0">
+ <Project Name="core" Path="core/core.project" Active="Yes"/>
+ <Project Name="rdp" Path="rdp/rdp.project" Active="No"/>
+ <BuildMatrix>
+ <WorkspaceConfiguration Name="Debug" Selected="yes">
+ <Environment/>
+ <Project Name="core" ConfigName="Debug"/>
+ <Project Name="rdp" ConfigName="Debug"/>
+ </WorkspaceConfiguration>
+ <WorkspaceConfiguration Name="Release" Selected="no">
+ <Environment/>
+ <Project Name="core" ConfigName="Release"/>
+ <Project Name="rdp" ConfigName="Release"/>
+ </WorkspaceConfiguration>
+ </BuildMatrix>
+</CodeLite_Workspace>
diff --git a/src/core/.clang-format b/src/core/.clang-format
new file mode 120000
index 0000000..2d11237
--- /dev/null
+++ b/src/core/.clang-format
@@ -0,0 +1 @@
+../../.clang-format \ No newline at end of file
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
new file mode 100644
index 0000000..162f75e
--- /dev/null
+++ b/src/core/CMakeLists.txt
@@ -0,0 +1,167 @@
+# -*- CMakeLists.txt generated by CodeLite IDE. Do not edit by hand -*-
+
+cmake_minimum_required(VERSION 2.8.11)
+
+# Project name
+project(core)
+
+# This setting is useful for providing JSON file used by CodeLite for code completion
+set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
+
+set(CONFIGURATION_NAME "Debug")
+
+# Define some variables
+set(PROJECT_core_PATH "${CMAKE_CURRENT_LIST_DIR}")
+set(WORKSPACE_PATH "${CMAKE_CURRENT_LIST_DIR}/..")
+
+
+
+#{{{{ User Code 1
+# Place your code here
+#}}}}
+
+include_directories(
+ .
+ ../../3rdparty/json.h
+ ./include
+ ../rdp/include
+ ../../3rdparty/wslay/lib/includes
+ ../../3rdparty/wslay/build/lib/includes
+ ../../3rdparty/libev
+ ../../3rdparty/picohttpparser
+ ../../3rdparty/libcb/include
+ ../../3rdparty/curl/build/lib
+ ../../3rdparty/curl/include
+)
+
+
+# Compiler options
+add_definitions(-Wall)
+add_definitions(-std=c99)
+add_definitions(-fno-strict-aliasing)
+add_definitions(-pthread)
+add_definitions(-D_POSIX_C_SOURCE=200112L)
+add_definitions(-D_XOPEN_SOURCE=500)
+add_definitions(-D_GNU_SOURCE)
+
+# Linker options
+#set(LINK_OPTIONS -pthread)
+
+
+link_libraries(
+"-pthread \
+-lssl \
+-lcrypto \
+-ldl \
+-lm \
+-lrt \
+-lpng \
+-lz \
+../../rdp/build/librdp.a \
+../../../3rdparty/libev/build/libev.a \
+../../../3rdparty/wslay/build/lib/libwslay.a \
+../../../3rdparty/FreeRDP/build/client/common/libfreerdp-client3.a \
+../../../3rdparty/FreeRDP/build/channels/rdpgfx/client/librdpgfx-client.a \
+../../../3rdparty/FreeRDP/build/channels/video/client/libvideo-client.a \
+../../../3rdparty/FreeRDP/build/channels/geometry/client/libgeometry-client.a \
+../../../3rdparty/FreeRDP/build/channels/parallel/client/libparallel-client.a \
+../../../3rdparty/FreeRDP/build/channels/drive/client/libdrive-client.a \
+../../../3rdparty/FreeRDP/build/channels/cliprdr/client/libcliprdr-client.a \
+../../../3rdparty/FreeRDP/build/channels/drdynvc/client/libdrdynvc-client.a \
+../../../3rdparty/FreeRDP/build/channels/remdesk/client/libremdesk-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpei/client/librdpei-client.a \
+../../../3rdparty/FreeRDP/build/channels/encomsp/client/libencomsp-client.a \
+../../../3rdparty/FreeRDP/build/channels/disp/client/libdisp-client.a \
+../../../3rdparty/FreeRDP/build/channels/echo/client/libecho-client.a \
+../../../3rdparty/FreeRDP/build/channels/rail/client/librail-client.a \
+../../../3rdparty/FreeRDP/build/channels/serial/client/libserial-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/librdpsnd-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake/librdpsnd-client-fake.a \
+../../../3rdparty/FreeRDP/build/channels/smartcard/client/libsmartcard-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdp2tcp/client/librdp2tcp-client.a \
+../../../3rdparty/FreeRDP/build/channels/ainput/client/libainput-client.a \
+../../../3rdparty/FreeRDP/build/channels/rdpdr/client/librdpdr-client.a \
+../../../3rdparty/FreeRDP/build/client/common/libfreerdp-client3.a \
+../../../3rdparty/FreeRDP/build/libfreerdp/libfreerdp3.a \
+../../../3rdparty/FreeRDP/build/winpr/libwinpr/libwinpr3.a \
+../../../3rdparty/curl/build/libcurl.a \
+-ldl"
+)
+if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+ add_definitions(-DDEBUG)
+ link_libraries(
+ "-pthread \
+ -lssl \
+ -lcrypto \
+ -lm \
+ -lrt \
+ -lpng \
+ -lz \
+ -lunwind \
+ -lunwind-x86_64 \
+ ../../rdp/build/librdp.a \
+ ../../../3rdparty/libev/build/libev.a \
+ ../../../3rdparty/wslay/build/lib/libwslay.a \
+ ../../../3rdparty/FreeRDP/build/client/common/libfreerdp-client3.a \
+ ../../../3rdparty/FreeRDP/build/channels/geometry/client/libgeometry-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpgfx/client/librdpgfx-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/video/client/libvideo-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/parallel/client/libparallel-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/drive/client/libdrive-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/cliprdr/client/libcliprdr-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/drdynvc/client/libdrdynvc-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/remdesk/client/libremdesk-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpei/client/librdpei-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/encomsp/client/libencomsp-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/disp/client/libdisp-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/echo/client/libecho-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rail/client/librail-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/serial/client/libserial-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/librdpsnd-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake/librdpsnd-client-fake.a \
+ ../../../3rdparty/FreeRDP/build/channels/smartcard/client/libsmartcard-client.a \
+ ../../../3rdparty/FreeRDP/build/channels/rdp2tcp/client/librdp2tcp-client.a \
+ ../../../3rdparty/FreeRDP/build/libfreerdp/libfreerdp3.a \
+ ../../../3rdparty/FreeRDP/build/winpr/libwinpr/libwinpr3.a \
+ ../../../3rdparty/curl/build/libcurl.a \
+ -ldl"
+ )
+endif()
+
+
+# Library path
+set(CMAKE_LDFLAGS "${CMAKE_LDFLAGS} -L. ")
+
+# Define the C sources
+set ( C_SRCS
+ ${CMAKE_CURRENT_LIST_DIR}/config_file.c
+ ${CMAKE_CURRENT_LIST_DIR}/cmdline.c
+ ${CMAKE_CURRENT_LIST_DIR}/ws_protocol.c
+ ${CMAKE_CURRENT_LIST_DIR}/ev_loop.c
+ ${CMAKE_CURRENT_LIST_DIR}/main.c
+ ${CMAKE_CURRENT_LIST_DIR}/utilities.c
+ ${CMAKE_CURRENT_LIST_DIR}/../../3rdparty/picohttpparser/picohttpparser.c
+ ${CMAKE_CURRENT_LIST_DIR}/thread_impl.c
+ ${CMAKE_CURRENT_LIST_DIR}/exports.c
+ ${CMAKE_CURRENT_LIST_DIR}/wrdp_thpool.c
+ ${CMAKE_CURRENT_LIST_DIR}/ws_session.c
+ ${CMAKE_CURRENT_LIST_DIR}/json_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/curl_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/backend_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/remote_control.c
+ ${CMAKE_CURRENT_LIST_DIR}/socket_helpers.c
+ ${CMAKE_CURRENT_LIST_DIR}/thread_sync.c
+ ${CMAKE_CURRENT_LIST_DIR}/log.c
+)
+
+set_source_files_properties(
+ ${C_SRCS} PROPERTIES COMPILE_FLAGS
+ " -std=c99 -Wall -fno-strict-aliasing -pthread -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=500 -D_GNU_SOURCE")
+
+if(WIN32)
+ enable_language(RC)
+ set(CMAKE_RC_COMPILE_OBJECT
+ "<CMAKE_RC_COMPILER> ${RC_OPTIONS} -O coff -i <SOURCE> -o <OBJECT>")
+endif(WIN32)
+
+add_executable(core ${RC_SRCS} ${CXX_SRCS} ${C_SRCS})
diff --git a/src/core/backend_helpers.c b/src/core/backend_helpers.c
new file mode 100644
index 0000000..ab40fac
--- /dev/null
+++ b/src/core/backend_helpers.c
@@ -0,0 +1,459 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "globals.h"
+#include "ws_session.h"
+#include "wrdp_thpool_internals.h"
+#include "webrdp_module_api.h"
+#include "task.h"
+#include "rdp_backend_api.h"
+#include "thread_sync.h"
+#include "utilities.h"
+#include "thread_impl.h"
+#include "curl_helpers.h"
+
+#include <errno.h>
+#include "base64_url.h"
+
+bool
+backend_validate(wrdp_backend_module *backend)
+{
+ if (!backend->callbacks_module->init)
+ {
+ return false;
+ }
+ if (!backend->callbacks_input->kcomb
+ && !backend->callbacks_input->kpress
+ && !backend->callbacks_input->kupdown
+ && !backend->callbacks_input->mouse
+ && !backend->callbacks_input->unicode)
+ {
+ /* backend can't handle any input */
+ return false;
+ }
+ return true;
+}
+
+static bool
+validate_backend_name(const char *name)
+{
+ if (!strcmp(name, "rdp"))
+ {
+ return true;
+ }
+ return false;
+}
+
+void
+backend_remove_from_pool(wrdp_backend_module *backend)
+{
+ wrdp_thpool_task *task = backend->wrdp_thpool_task;
+
+ user_pool_msg *pmsg = calloc(1, sizeof(user_pool_msg));
+ if (!pmsg)
+ {
+ perror("calloc");
+ return;
+ }
+ pmsg->type = msg_type_destroy_ws_backend_info;
+ pmsg->backend = backend;
+
+ wrdp_thpool_send_msg_to_pool(task->thread->pool, pmsg);
+}
+
+static bool
+backend_create(const char *name, ws_session *session)
+{
+ wrdp_backend_module *backend = 0;
+ task_info *info = session->task_info;
+ wrdp_thpool_task *task = 0;
+
+ if (session->sid_base64)
+ {
+ size_t sid_len = strlen(session->sid_base64);
+ char *attach_sid = malloc(sid_len + 1);
+ memcpy(attach_sid, session->sid_base64, sid_len);
+ attach_sid[sid_len] = 0;
+ session->attach_sid_base64 = attach_sid;
+ }
+
+ if (!validate_backend_name(name))
+ {
+ return false;
+ }
+ backend = calloc(1, sizeof(wrdp_backend_module));
+ if (!backend)
+ {
+ perror("calloc");
+ return false;
+ }
+ bool module_initialized = false;
+ info->backend = backend;
+ backend->task_info = info;
+ session->task_info = info;
+ info->wrdp_thpool_task = session->wrdp_thpool_task;
+ backend->wrdp_thpool_task = session->wrdp_thpool_task;
+ task = session->wrdp_thpool_task;
+ task->userdata = info;
+ {
+ struct ws_session_list_entry_s *entry
+ = calloc(1, sizeof(struct ws_session_list_entry_s));
+ if (!entry)
+ {
+ perror("calloc");
+ goto error;
+ }
+ entry->session = session;
+ backend->sessions_list_head
+ = calloc(1, sizeof(SLIST_HEAD(h, ws_session_list_entry_s)));
+ SLIST_HEAD(sessions_head,
+ ws_session_list_entry_s) *sessions_list_head_p
+ = backend->sessions_list_head;
+ SLIST_INIT(sessions_list_head_p);
+ SLIST_INSERT_HEAD(sessions_list_head_p, entry, entries);
+ info->settings = g_globals.settings.ws_session_defaults;
+ }
+ SLIST_INIT(&(session->curls_easy_head));
+
+ backend->callbacks_input = calloc(1, sizeof(wrdp_backend_cb_input));
+ if (!backend->callbacks_input)
+ {
+ perror("calloc");
+ goto error;
+ }
+ backend->callbacks_module = calloc(1, sizeof(wrdp_backend_cb_module));
+ if (!backend->callbacks_module)
+ {
+ perror("calloc");
+ goto error;
+ }
+ backend->callbacks_clipbrd
+ = calloc(1, sizeof(wrdp_backend_cb_clipboard));
+ if (!backend->callbacks_clipbrd)
+ {
+ perror("calloc");
+ goto error;
+ }
+ backend->callbacks_ft = calloc(1, sizeof(wrdp_backend_cb_filetransfer));
+ if (!backend->callbacks_ft)
+ {
+ perror("calloc");
+ goto error;
+ }
+ if (!strcmp(name, "rdp"))
+ {
+ module_initialized = rdp_create(g_globals.exports, backend);
+ if (!module_initialized)
+ {
+ goto error;
+ }
+ }
+ strcpy(info->backend_name, name);
+ info->backend = backend;
+ if (!backend_validate(info->backend))
+ {
+ info->backend = 0;
+ goto error;
+ }
+ info->backend->callbacks_module->set_task_info(
+ info, info->backend->backend_internals);
+ {
+ /* TODO: do it in main thread */
+ user_pool_msg *pmsg = calloc(1, sizeof(user_pool_msg));
+ wrdp_thpool_task *task = session->wrdp_thpool_task;
+ if (!pmsg)
+ {
+ perror("calloc");
+ goto error;
+ }
+ pmsg->type = msg_type_backend_created;
+ pmsg->backend = backend;
+ wrdp_thpool_send_msg_to_pool(task->thread->pool, pmsg);
+ }
+ {
+ wrdp_thpool_task *task = session->wrdp_thpool_task;
+ info->ev_timer_watcher = calloc(1, sizeof(ev_timer));
+ if (!info->ev_timer_watcher)
+ {
+ perror("calloc");
+ goto error;
+ }
+ ev_timer_init(
+ info->ev_timer_watcher, task_timeouts_check_cb, 1., 0.);
+ info->ev_timer_watcher->data = info;
+ info->ev_timer_watcher->repeat = 1;
+ ev_timer_again(
+ task->thread->ev_th_loop, info->ev_timer_watcher);
+ }
+ return true;
+error:
+ if (backend)
+ {
+ if (backend->callbacks_input)
+ {
+ free(backend->callbacks_input);
+ }
+ if (backend->callbacks_module)
+ {
+ free(backend->callbacks_module);
+ }
+ if (backend->callbacks_clipbrd)
+ {
+ free(backend->callbacks_clipbrd);
+ }
+ if (backend->callbacks_ft)
+ {
+ free(backend->callbacks_ft);
+ }
+ backend_remove_from_pool(backend);
+ }
+ return false;
+}
+
+void
+backend_destroy(wrdp_backend_module *backend)
+{
+ if (backend->callbacks_input)
+ {
+ free(backend->callbacks_input);
+ }
+ if (backend->callbacks_module)
+ {
+ free(backend->callbacks_module);
+ }
+ if (backend->callbacks_ft)
+ {
+ free(backend->callbacks_ft);
+ }
+ if (backend->callbacks_clipbrd)
+ {
+ free(backend->callbacks_clipbrd);
+ }
+ if (backend->sessions_list_head)
+ {
+ free(backend->sessions_list_head);
+ }
+ backend_remove_from_pool(backend);
+}
+
+extern void task_destroy_timers(wrdp_thpool_task *task);
+
+void
+backend_task_destroy(wrdp_thpool_task *task)
+{
+ task_destroy_timers(task);
+
+ task_info *info = task->userdata;
+ backend_destroy(info->backend);
+
+ free(info);
+}
+
+static void
+ws_session_child_init_cb(wrdp_thpool_task *task, void *userdata)
+{
+ ws_session *session = userdata;
+ session->wrdp_thpool_task = task;
+}
+
+static bool
+backend_find(ws_session *session)
+{
+ for (struct backend_s *t1 = LIST_FIRST(&(g_globals.backends_head)); t1;
+ t1 = LIST_NEXT(t1, entries))
+ {
+ //TODO: WTF?
+ if (!t1->backend)
+ continue;
+
+ SLIST_HEAD(sessions_head,
+ ws_session_list_entry_s) *sessions_list_head_p
+ = t1->backend->sessions_list_head;
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->session && session->attach_sid_base64
+ && s->session->sid_base64
+ && !strcmp(session->attach_sid_base64,
+ s->session->sid_base64))
+ {
+ if (session == s->session)
+ {
+ return true;
+ }
+ struct ws_session_list_entry_s *entry = calloc(
+ 1, sizeof(struct ws_session_list_entry_s));
+ wrdp_thpool_task *t;
+ if (!entry)
+ {
+ session->session_state
+ = ws_session_error;
+ perror("calloc");
+ return false;
+ }
+ entry->session = session;
+ session->task_info = t1->backend->task_info;
+ SLIST_INSERT_HEAD(
+ sessions_list_head_p, entry, entries);
+ t = t1->backend->wrdp_thpool_task;
+ if (!wrdp_thread_pool_move_task_to_thread(
+ g_globals.thpool, ws_run_session,
+ ws_stop_session, t->thread->thread_id,
+ ws_session_child_init_cb,
+ session->wrdp_thpool_task, session))
+ {
+ /* TODO: cleanup ? */
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool
+backend_get(const char *name, ws_session *session)
+{
+ bool found = false;
+ if (session->attach_sid_base64)
+ {
+ found = backend_find(session);
+ }
+ else
+ {
+ found = backend_create(name, session);
+ }
+ return found;
+}
+
+void
+backend_fill_settings(ws_session *session)
+{
+ while (!SLIST_EMPTY(&(session->backend_settings_head)))
+ {
+ task_info *info = session->task_info;
+ struct backend_setting_s *s
+ = SLIST_FIRST(&(session->backend_settings_head));
+ SLIST_REMOVE_HEAD(&(session->backend_settings_head), entries);
+ if (s->type == setting_int)
+ {
+ info->backend->callbacks_module->set_setting_int(
+ &(s->setting_int),
+ info->backend->backend_internals);
+ free(s->setting_int.name);
+ free(s);
+ }
+ else if (s->type == setting_string)
+ {
+ info->backend->callbacks_module->set_setting_str(
+ &(s->setting_string),
+ info->backend->backend_internals);
+ free(s->setting_string.name);
+ free(s->setting_string.value);
+ free(s);
+ }
+ }
+}
+
+bool
+handle_backend_setting_int(const char *name, int64_t val, ws_session *session)
+{
+ task_info *info = session->task_info;
+ if (!info || !info->backend)
+ {
+ if (!SLIST_EMPTY(&(session->backend_settings_head)))
+ {
+ for (struct backend_setting_s *s
+ = SLIST_FIRST(&(session->backend_settings_head));
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->type != setting_int)
+ continue;
+ if (strcmp(s->setting_int.name, name))
+ continue;
+ s->setting_int.value = val;
+ return true;
+ }
+ }
+ struct backend_setting_s *s
+ = calloc(1, sizeof(struct backend_setting_s));
+ if (!s)
+ {
+ perror("calloc");
+ return false;
+ }
+ s->setting_int.name = strdup(name);
+ s->setting_int.value = val;
+ s->type = setting_int;
+ SLIST_INSERT_HEAD(
+ &(session->backend_settings_head), s, entries);
+ }
+ else
+ {
+ backend_setting_int s;
+ s.name = (char *)name;
+ s.value = val;
+ if (!info->backend->callbacks_module->set_setting_int(
+ &s, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+handle_backend_setting_string(
+ const char *name, const char *val, ws_session *session)
+{
+ task_info *info = session->task_info;
+ if (!info || !info->backend)
+ {
+ if (!SLIST_EMPTY(&(session->backend_settings_head)))
+ {
+ for (struct backend_setting_s *s
+ = SLIST_FIRST(&(session->backend_settings_head));
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->type != setting_string)
+ continue;
+ if (strcmp(s->setting_string.name, name))
+ continue;
+ free(s->setting_string.value);
+ s->setting_string.value = strdup(val);
+ return true;
+ }
+ }
+ struct backend_setting_s *s
+ = calloc(1, sizeof(struct backend_setting_s));
+ if (!s)
+ {
+ perror("calloc");
+ return false;
+ }
+ s->setting_string.name = strdup(name);
+ s->setting_string.value = strdup(val);
+ s->type = setting_string;
+ SLIST_INSERT_HEAD(
+ &(session->backend_settings_head), s, entries);
+ }
+ else
+ {
+ backend_setting_str s;
+ s.name = (char *)name;
+ s.value = (char *)val;
+ info->backend->callbacks_module->set_setting_str(
+ &s, info->backend->backend_internals);
+ }
+ return true;
+}
diff --git a/src/core/backend_helpers.h b/src/core/backend_helpers.h
new file mode 100644
index 0000000..c93be98
--- /dev/null
+++ b/src/core/backend_helpers.h
@@ -0,0 +1,20 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+bool backend_validate(wrdp_backend_module *backend);
+
+bool backend_get(const char *name, ws_session *session);
+void backend_destroy(wrdp_backend_module *backend);
+
+void backend_fill_settings(ws_session *session);
+
+bool handle_backend_setting_int(
+ const char *name, int64_t val, ws_session *session);
+
+bool handle_backend_setting_string(
+ const char *name, const char *val, ws_session *session);
diff --git a/src/core/base64_url.h b/src/core/base64_url.h
new file mode 100644
index 0000000..0f7741a
--- /dev/null
+++ b/src/core/base64_url.h
@@ -0,0 +1,187 @@
+/*-
+ * Copyright (c) 2003 - 2016 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * Copyright (c) 2018 sss <sss at dark-alexandr dot net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#pragma once
+
+static const uint8_t *base64_url_tbl_coding = (const uint8_t
+ *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+static const uint8_t base64_url_tbl_decoding[256] = {64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
+ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, 64, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64};
+
+static inline int
+base64_url_encode(uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_size,
+ size_t *enc_size)
+{
+ size_t tm, src_m3_size;
+ register uint8_t *wpos, *rpos, *src_m3_max;
+
+ if (NULL == src || 0 == src_size)
+ return (EINVAL);
+ /* dst buf size calculation. */
+ tm = (src_size / 3);
+ src_m3_size = (tm * 3);
+ if (src_m3_size != src_size)
+ { /* is multiple of 3? */
+ tm++;
+ }
+ tm *= 4;
+ if (NULL != enc_size)
+ {
+ (*enc_size) = tm;
+ }
+ if (dst_size < tm) /* Is dst buf too small? */
+ return (ENOBUFS);
+ if (NULL == dst)
+ return (EINVAL);
+ wpos = dst;
+ rpos = src;
+ /* Main loop: encode 3 -> 4 */
+ for (src_m3_max = (src + src_m3_size); rpos < src_m3_max; rpos += 3)
+ {
+ (*wpos++) = base64_url_tbl_coding[rpos[0] >> 2]; /* c1 */
+ (*wpos++)
+ = base64_url_tbl_coding[((rpos[0] << 4) & 0x30)
+ | ((rpos[1] >> 4) & 0x0f)]; /* c2 */
+ (*wpos++)
+ = base64_url_tbl_coding[((rpos[1] << 2) & 0x3c)
+ | ((rpos[2] >> 6) & 0x03)]; /* c3 */
+ (*wpos++) = base64_url_tbl_coding[rpos[2] & 0x3f]; /* c4 */
+ }
+ /* Tail special encoding. */
+ if (src_size != src_m3_size)
+ { /* If src_size was not a multiple of 3: 1-2 bytes tail special coding.
+ */
+ (*wpos++) = base64_url_tbl_coding[rpos[0] >> 2]; /* c1 */
+ if (1 == (src_size - src_m3_size))
+ { /* 1 byte tail. */
+ (*wpos++) = base64_url_tbl_coding[(
+ (rpos[0] << 4) & 0x30)]; /* c2 */
+ (*wpos++) = '='; /* c3: tail padding. */
+ }
+ else
+ { /* 2 bytes tail. */
+ (*wpos++)
+ = base64_url_tbl_coding[((rpos[0] << 4) & 0x30)
+ | ((rpos[1] >> 4)
+ & 0x0f)]; /* c2 */
+ (*wpos++) = base64_url_tbl_coding[(
+ (rpos[1] << 2) & 0x3c)]; /* c3 */
+ }
+ (*wpos++) = '='; /* c4: tail padding. */
+ }
+ (*wpos) = 0;
+#if 0
+ if ((wpos - dst) != tm) { /* Must be euqual! */
+ (*enc_size) = (wpos - dst);
+ }
+#endif
+
+ return (0);
+}
+
+static inline int
+base64_url_decode(uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_size,
+ size_t *dcd_size)
+{
+ size_t tm, src_m4_size;
+ register uint8_t *wpos, *rpos, *src_m4_max;
+
+ if (NULL == src || 2 > src_size)
+ return (EINVAL);
+ /* Remove tail padding. */
+ for (; 0 < src_size; src_size--)
+ {
+ if ('=' != src[(src_size - 1)])
+ break;
+ }
+ if (2 > src_size) /* Check again: at least 2 byte needed for decoder. */
+ return (EINVAL);
+ /* dst buf size calculation. */
+ tm = (src_size / 4);
+ src_m4_size = (tm * 4);
+ if (src_m4_size != src_size)
+ { /* is multiple of 4? */
+ tm++;
+ }
+ tm *= 3;
+ if (dst_size < tm)
+ { /* Is dst buf too small? */
+ if (NULL != dcd_size)
+ {
+ (*dcd_size) = tm;
+ }
+ return (ENOBUFS);
+ }
+ if (NULL == dst)
+ return (EINVAL);
+ wpos = dst;
+ rpos = src;
+ /* Main loop: decode 4 -> 3 */
+ for (src_m4_max = (src + src_m4_size); rpos < src_m4_max; rpos += 4)
+ {
+ (*wpos++) = (base64_url_tbl_decoding[rpos[0]] << 2
+ | base64_url_tbl_decoding[rpos[1]] >> 4);
+ (*wpos++) = (base64_url_tbl_decoding[rpos[1]] << 4
+ | base64_url_tbl_decoding[rpos[2]] >> 2);
+ (*wpos++) = (base64_url_tbl_decoding[rpos[2]] << 6
+ | base64_url_tbl_decoding[rpos[3]]);
+ }
+ /* Tail special decoding. */
+ switch ((src_size - src_m4_size))
+ {
+ case 2:
+ (*wpos++) = (base64_url_tbl_decoding[rpos[0]] << 2
+ | base64_url_tbl_decoding[rpos[1]] >> 4);
+ break;
+ case 3:
+ (*wpos++) = (base64_url_tbl_decoding[rpos[0]] << 2
+ | base64_url_tbl_decoding[rpos[1]] >> 4);
+ (*wpos++) = (base64_url_tbl_decoding[rpos[1]] << 4
+ | base64_url_tbl_decoding[rpos[2]] >> 2);
+ break;
+ }
+ (*wpos) = 0;
+ if (NULL != dcd_size)
+ { /* Real decoded size can be smaller than calculated. */
+ (*dcd_size) = (wpos - dst);
+ }
+
+ return (0);
+}
diff --git a/src/core/cmdline.c b/src/core/cmdline.c
new file mode 100644
index 0000000..b37e1d7
--- /dev/null
+++ b/src/core/cmdline.c
@@ -0,0 +1,122 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config_file.h"
+#include "globals.h"
+
+static void
+print_help(const char *prog_path)
+{
+ printf("Usage: %s [-c:d....]\nOptions:\n"
+ "\t-c\t--config_file\t\"Use settings from"
+ " specified file instead of default config file\"\n"
+ "\t-d\t--daemon\t\"Daemon mode (fork to background)\"\n"
+ "\t--http_port\t\"Set port for http/ws server\"\n"
+ "\t--help\t\"Print tthis help message\"\n",
+ prog_path);
+ exit(EXIT_FAILURE);
+}
+
+void
+handle_cmdline_args(int argc, char **argv)
+{
+ int c;
+ char config_file_path[_POSIX_PATH_MAX] = {0};
+ bool daemon = false, wrong_options = false;
+ while (1)
+ {
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"config_file", required_argument, 0, 0},
+ {"daemon", no_argument, 0, 0},
+ {"ws_port", required_argument, 0, 0},
+ {"help", no_argument, 0, 0}
+ };
+ c = getopt_long(
+ argc, argv, "c:dp:", long_options, &option_index);
+ if (c == -1)
+ break;
+ switch (c)
+ {
+ case 0:
+ {
+ if (!strcmp(long_options[option_index].name,
+ "config_file")
+ && optarg)
+ {
+ strncpy(config_file_path, optarg,
+ _POSIX_PATH_MAX - 1);
+ config_file_path[_POSIX_PATH_MAX - 1]
+ = 0;
+ }
+ else if (!strcmp(
+ long_options[option_index].name,
+ "ws_port")
+ && optarg)
+ g_globals.settings.ws_port
+ = atoi(optarg);
+ else if (!strcmp(
+ long_options[option_index].name,
+ "daemon"))
+ daemon = true;
+ else if (!strcmp(
+ long_options[option_index].name,
+ "help"))
+ print_help(argv[0]);
+ }
+ break;
+ case 'c':
+ {
+ if (optarg) //clang static analyzer...
+ {
+ strncpy(config_file_path, optarg,
+ _POSIX_PATH_MAX - 1);
+ config_file_path[_POSIX_PATH_MAX - 1]
+ = 0;
+ }
+ }
+ break;
+ case 'p':
+ {
+ if (optarg) //clang static analyzer...
+ {
+ g_globals.settings.ws_port
+ = atoi(optarg);
+ }
+ }
+ break;
+ case 'd':
+ {
+ daemon = true;
+ }
+ break;
+ case '?':
+ {
+ wrong_options = true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (wrong_options)
+ {
+ print_help(argv[0]);
+ }
+ else
+ {
+ g_globals.settings.daemon = daemon;
+ find_config_file(config_file_path);
+ }
+}
diff --git a/src/core/cmdline.h b/src/core/cmdline.h
new file mode 100644
index 0000000..26142d9
--- /dev/null
+++ b/src/core/cmdline.h
@@ -0,0 +1,9 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void handle_cmdline_args(int argc, char **argv);
diff --git a/src/core/config_file.c b/src/core/config_file.c
new file mode 100644
index 0000000..e09fb5d
--- /dev/null
+++ b/src/core/config_file.c
@@ -0,0 +1,480 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <limits.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+//json.h lib
+#include <json.h>
+#include "json_helpers.h"
+
+#include <errno.h>
+#include "base64_url.h"
+
+#include "globals.h"
+#include "utilities.h"
+
+#include "webrdp_core_api.h"
+
+enum option_type
+{
+ option_type_bool,
+ option_type_string,
+ option_type_int,
+ option_type_unsupported
+};
+
+struct option_s
+{
+ char name[128];
+ union
+ {
+ char val_string[260];
+ bool val_bool;
+ int64_t val_int;
+ };
+ enum option_type type;
+};
+
+static int64_t
+json_cfg_option_extract_int64(struct option_s *option)
+{
+ switch (option->type)
+ {
+ case option_type_int:
+ return option->val_int;
+ break;
+ case option_type_string:
+ return atoll(option->val_string);
+ break;
+ default:
+ return 0;
+ }
+ return 0;
+}
+
+static bool
+json_cfg_option_extract_bool(struct option_s *option)
+{
+ switch (option->type)
+ {
+ case option_type_bool:
+ return option->val_bool;
+ break;
+ case option_type_int:
+ return option->val_int;
+ break;
+ case option_type_string:
+ return atoi(option->val_string);
+ break;
+ default:
+ return false;
+ break;
+ }
+ return false;
+}
+
+static void
+handle_session_option(struct option_s *option)
+{
+ if (!strcmp(option->name, "session_time_limit"))
+ {
+ g_globals.settings.ws_session_defaults.session_time_limit
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "session_idle_timeout"))
+ {
+ g_globals.settings.ws_session_defaults.session_idle_timeout
+ = json_cfg_option_extract_int64(option);
+ }
+}
+
+static uint8_t
+get_log_level(const char *log_level_str)
+{
+ uint8_t lvl = wrdp_log_level_error;
+ if (!strcmp(log_level_str, "error"))
+ {
+ lvl = wrdp_log_level_error;
+ }
+ else if (!strcmp(log_level_str, "warning"))
+ {
+ lvl = wrdp_log_level_warning;
+ }
+ else if (!strcmp(log_level_str, "info"))
+ {
+ lvl = wrdp_log_level_info;
+ }
+ else if (!strcmp(log_level_str, "debug"))
+ {
+ lvl = wrdp_log_level_debug;
+ }
+ else if (!strcmp(log_level_str, "trace"))
+ {
+ lvl = wrdp_log_level_trace;
+ }
+#ifndef DEBUG
+ if (lvl > wrdp_log_level_info)
+ {
+ printf(
+ "log_level higher than info does not supported for relase"
+ " builds, downgrade to info\n");
+ lvl = wrdp_log_level_info;
+ }
+#endif /* DEBUG */
+ return lvl;
+}
+
+static void
+handle_global_option(struct option_s *option)
+{
+ if (!strcmp(option->name, "daemon"))
+ {
+ if (!g_globals.settings.daemon)
+ {
+ g_globals.settings.daemon
+ = json_cfg_option_extract_bool(option);
+ }
+ }
+ else if (!strcmp(option->name, "log_level"))
+ {
+ g_globals.settings.log_level
+ = get_log_level(option->val_string);
+ }
+ else if (!strcmp(option->name, "thread_count"))
+ {
+ g_globals.settings.thread_count
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "tasks_per_thread"))
+ {
+ g_globals.settings.tasks_per_thread
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "ws_port"))
+ {
+ g_globals.settings.ws_port
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "ws_socket_path"))
+ {
+ g_globals.settings.ws_socket_path = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_port"))
+ {
+ g_globals.settings.ctl_port
+ = json_cfg_option_extract_int64(option);
+ }
+ else if (!strcmp(option->name, "ctl_socket_path"))
+ {
+ g_globals.settings.ctl_socket_path = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_cafile"))
+ {
+ g_globals.settings.ctl_ssl_cafile = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_capath"))
+ {
+ g_globals.settings.ctl_ssl_capath = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_cert"))
+ {
+ g_globals.settings.ctl_ssl_cert = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "ctl_ssl_key"))
+ {
+ g_globals.settings.ctl_ssl_key = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "auth_server_url"))
+ {
+ g_globals.settings.auth_server_url = strdup(option->val_string);
+ }
+ else if (!strcmp(option->name, "secret_key_verify"))
+ {
+ size_t dec_size = 0;
+ errno = base64_url_decode((uint8_t *)(option->val_string),
+ strlen(option->val_string),
+ (uint8_t *)g_globals.settings.secret_key_verify, 66,
+ &dec_size);
+ if (errno)
+ {
+ perror("handle_global_option: base64_url_decode");
+ exit(EXIT_FAILURE);
+ }
+ if (dec_size != 64)
+ {
+ printf("Failed to decode verification secret key"
+ " (wrong decoded key length)\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ else if (!strcmp(option->name, "secret_key_sign"))
+ {
+ size_t dec_size = 0;
+ errno = base64_url_decode((uint8_t *)(option->val_string),
+ strlen(option->val_string),
+ (uint8_t *)g_globals.settings.secret_key_sign, 66,
+ &dec_size);
+ if (errno)
+ {
+ perror("handle_global_option: base64_url_decode");
+ exit(EXIT_FAILURE);
+ }
+ if (dec_size != 64)
+ {
+ printf("Failed to decode signing secret key"
+ " (wrong decoded key length)\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+static void
+handle_option(struct option_s *option, const char *prefix, size_t prefix_len)
+{
+ /* TODO: refactoring */
+ if (option->type == option_type_unsupported)
+ {
+ printf("Unsupported option type found in config file\n");
+ return;
+ }
+ if (prefix && prefix_len > 0)
+ {
+ size_t session_len = strlen("session");
+ if (session_len != prefix_len)
+ {
+ goto unknown_prefix;
+ }
+ if (!strncmp("session", prefix, prefix_len))
+ {
+ handle_session_option(option);
+ }
+ return;
+ unknown_prefix:
+ printf("Unknown second level option ignored\n");
+ return;
+ }
+ handle_global_option(option);
+}
+
+static void
+print_option(struct option_s *option, const char *prefix, size_t prefix_len)
+{
+ printf("Found option: ");
+ if (prefix && prefix_len > 0)
+ {
+ printf("%.*s.", (int)prefix_len, prefix);
+ }
+ printf("%s == ", option->name);
+ switch (option->type)
+ {
+ case option_type_bool:
+ {
+ if (option->val_bool)
+ printf("true");
+ else
+ printf("false");
+ printf(" (bool)\n");
+ }
+ break;
+ case option_type_int:
+ {
+ printf("%ld (int)\n", option->val_int);
+ }
+ break;
+ case option_type_string:
+ printf("%s (string)\n", option->val_string);
+ break;
+ case option_type_unsupported:
+ printf("unsupported option type\n");
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+handle_json_option(struct json_object_element_s *json_option,
+ const char *prefix, size_t prefix_len)
+{
+ struct option_s option;
+ memset(&option, 0, sizeof(struct option_s));
+ strncpy(option.name, json_option->name->string,
+ json_option->name->string_size);
+ option.name[json_option->name->string_size] = 0;
+ switch (json_option->value->type)
+ {
+ case json_type_false:
+ {
+ option.val_bool = false;
+ option.type = option_type_bool;
+ }
+ break;
+ case json_type_true:
+ {
+ option.val_bool = true;
+ option.type = option_type_bool;
+ }
+ break;
+ case json_type_number:
+ {
+ option.val_int = json_option_extract_int64(json_option);
+ option.type = option_type_int;
+ }
+ break;
+ case json_type_string:
+ {
+ json_option_extract_string(
+ json_option, (char *)&(option.val_string));
+ option.type = option_type_string;
+ }
+ break;
+ case json_type_object:
+ {
+ struct json_object_s *jsopt
+ = (struct json_object_s *)
+ json_option->value->payload;
+ struct json_object_element_s *jsoption = jsopt->start;
+ while (jsoption)
+ {
+ handle_json_option(jsoption,
+ json_option->name->string,
+ json_option->name->string_size);
+ jsoption = jsoption->next;
+ }
+ return;
+ }
+ break;
+ default:
+ {
+ option.type = option_type_unsupported;
+ }
+ break;
+ }
+ handle_option(&option, prefix, prefix_len);
+ print_option(&option, prefix, prefix_len);
+}
+
+static void
+parse_json(char *buf, size_t buf_size)
+{
+ struct json_value_s *root = json_parse(buf, buf_size);
+ if (!root)
+ {
+ printf("Failed to parse config (not valid json ?)\n");
+ exit(EXIT_FAILURE);
+ }
+ struct json_object_s *object = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ handle_json_option(option, 0, 0);
+ option = option->next;
+ }
+ free(buf);
+ free(root);
+}
+
+static void
+parse_config_file(const char *path)
+{
+ char *buf = 0;
+ FILE *cfg = 0;
+ size_t size = 0;
+ {
+ struct stat st;
+ stat(path, &st);
+ size = st.st_size;
+
+ buf = (char *)malloc(size);
+ if (!buf)
+ {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+ }
+ cfg = fopen(path, "r");
+ if (fread(buf, 1, size, cfg) != size)
+ {
+ printf("Failed to read config file\n");
+ fclose(cfg);
+ exit(EXIT_FAILURE);
+ }
+ fclose(cfg);
+ printf("\tLoaded.\n");
+ parse_json(buf, size);
+}
+
+void
+find_config_file(char *config_file_path)
+{
+ bool file_exists = false;
+ if (config_file_path[0])
+ {
+ printf("Trying to load config file from specified path: %s...",
+ config_file_path);
+ if (!is_regular_file(config_file_path))
+ {
+ printf("\tFile does not exists or not regular file\n");
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ file_exists = true;
+ printf("\tFile exists...");
+ }
+ }
+ else
+ {
+ struct passwd *pw = getpwuid(getuid());
+ const char *homedir = pw->pw_dir;
+ snprintf(config_file_path, _POSIX_PATH_MAX - 1,
+ "%s/.config/%s/config", homedir, PROG_NAME);
+ printf("Trying to load config file from default path: %s...",
+ config_file_path);
+ if (!is_regular_file(config_file_path))
+ {
+ printf("\tFile does not exists or not regular file\n");
+ config_file_path[0] = 0;
+ snprintf(config_file_path, _POSIX_PATH_MAX - 1,
+ "/etc/%s/config", PROG_NAME);
+ }
+ else
+ {
+ file_exists = true;
+ printf("\tFile exists...");
+ }
+ if (!file_exists)
+ {
+ printf("Trying to load config file from default path: "
+ "%s...",
+ config_file_path);
+ if (!is_regular_file(config_file_path))
+ printf("\tFile does not exists or not regular "
+ "file\n");
+ else
+ {
+ file_exists = true;
+ printf("\tFile exists...");
+ }
+ }
+ if (!file_exists)
+ printf("Default config file not found and custom"
+ " config file not specififed, running with "
+ "internal "
+ "defaults.\n");
+ }
+ if (file_exists)
+ parse_config_file(config_file_path);
+}
diff --git a/src/core/config_file.h b/src/core/config_file.h
new file mode 100644
index 0000000..55d6413
--- /dev/null
+++ b/src/core/config_file.h
@@ -0,0 +1,9 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void find_config_file(char *config_file_path);
diff --git a/src/core/core.project b/src/core/core.project
new file mode 100644
index 0000000..16459f0
--- /dev/null
+++ b/src/core/core.project
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CodeLite_Project Name="core" Version="11000" InternalType="Console">
+ <Plugins>
+ <Plugin Name="qmake">
+ <![CDATA[00010001N0005Debug000000000000]]>
+ </Plugin>
+ </Plugins>
+ <Description/>
+ <Dependencies/>
+ <VirtualDirectory Name="src">
+ <File Name="task.h"/>
+ <File Name="log.c"/>
+ <File Name="log.h"/>
+ <File Name="thread_sync.h"/>
+ <File Name="thread_sync.c"/>
+ <File Name="ws_session.h"/>
+ <File Name="ws_session.c"/>
+ <File Name="ctl_task.h"/>
+ <File Name="socket_helpers.h"/>
+ <File Name="socket_helpers.c"/>
+ <File Name="remote_control.h"/>
+ <File Name="remote_control.c"/>
+ <File Name="base64_url.h"/>
+ <File Name="backend_helpers.h"/>
+ <File Name="backend_helpers.c"/>
+ <File Name="curl_helpers.c"/>
+ <File Name="curl_helpers.h"/>
+ <File Name="json_helpers.h"/>
+ <File Name="json_helpers.c"/>
+ <File Name="exports.h"/>
+ <File Name="exports.c"/>
+ <File Name="ws_protocol.h"/>
+ <File Name="ws_protocol.c"/>
+ <File Name="../../3rdparty/picohttpparser/picohttpparser.c"/>
+ <File Name="ws_server_internals.h"/>
+ <File Name="wrdp_thpool_internals.h"/>
+ <File Name="thread_impl.h"/>
+ <File Name="thread_impl.c"/>
+ <File Name="wrdp_thpool.h"/>
+ <File Name="wrdp_thpool.c"/>
+ <File Name="ev_loop.h"/>
+ <File Name="ev_loop.c"/>
+ <File Name="cmdline.h"/>
+ <File Name="cmdline.c"/>
+ <File Name="config_file.h"/>
+ <File Name="config_file.c"/>
+ <File Name="globals.h"/>
+ <VirtualDirectory Name="include">
+ <File Name="include/webrdp_api_utils.h"/>
+ <File Name="include/webrdp_api_shared_structures.h"/>
+ <File Name="include/webrdp_module_api.h"/>
+ <File Name="include/webrdp_core_api.h"/>
+ </VirtualDirectory>
+ <File Name="utilities.h"/>
+ <File Name="utilities.c"/>
+ <File Name="main.c"/>
+ </VirtualDirectory>
+ <Dependencies Name="Debug">
+ <Project Name="rdp"/>
+ </Dependencies>
+ <Dependencies Name="Release">
+ <Project Name="rdp"/>
+ </Dependencies>
+ <Settings Type="Executable">
+ <GlobalSettings>
+ <Compiler Options="" C_Options="-std=c99;-pthread;-D_POSIX_C_SOURCE=200112L;-D_XOPEN_SOURCE=500;-D_GNU_SOURCE;" Assembler="">
+ <IncludePath Value="."/>
+ <IncludePath Value="../../3rdparty/json.h"/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../rdp/include"/>
+ <IncludePath Value="../../3rdparty/wslay/lib/includes"/>
+ <IncludePath Value="../../3rdparty/wslay/build/lib/includes"/>
+ <IncludePath Value="../../3rdparty/libev"/>
+ <IncludePath Value="../../3rdparty/picohttpparser"/>
+ <IncludePath Value="../../3rdparty/libcb/include"/>
+ <IncludePath Value="../../3rdparty/curl/build/lib"/>
+ <IncludePath Value="../../3rdparty/curl/include"/>
+ </Compiler>
+ <Linker Options="-pthread;-rpath .;-fsanitize=address;-l:rdp.a;-l:libev.a;-l:libwslay.a;-l:libfreerdp3.a;-l:libwinpr3.a;-l:libfreerdp-client3.a;-l:libgeometry-client.a;-l:librdpgfx-client.a;-l:libvideo-client.a;-l:libparallel-client.a;-l:libdrive-client.a;-l:libcliprdr-client.a;-l:libdrdynvc-client.a;-l:libremdesk-client.a;-l:librdpei-client.a;-l:libencomsp-client.a;-l:libdisp-client.a;-l:libecho-client.a;-l:librail-client.a;-l:libserial-client.a;-l:librdpsnd-client.a;-l:librdpsnd-client-fake.a;-l:libsmartcard-client.a;-l:libfreerdp3.a;-l:librdpdr-client.a;-l:libwinpr3.a;-l:librdp2tcp-client.a;-l:libainput-client.a;-l:libcurl.a;-l:libfreerdp-client3.a">
+ <LibraryPath Value="."/>
+ <LibraryPath Value="./lib"/>
+ <LibraryPath Value="$(WorkspacePath)/rdp/$(ConfigurationName)/"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/libfreerdp"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/winpr/libwinpr"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/client/common"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/geometry/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpgfx/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/video/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/parallel/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/drive/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/cliprdr/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/drdynvc/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/remdesk/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpei/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/encomsp/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/audin/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/disp/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/echo/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rail/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/serial/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpsnd/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/smartcard/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/audin/client/oss"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpsnd/client/oss"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdp2tcp/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpdr/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/urbdrc/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/printer/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/ainput/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/FreeRDP/build/channels/rdpdr/client"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/curl/build"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/libev/build"/>
+ <LibraryPath Value="$(WorkspacePath)/../3rdparty/wslay/build/lib"/>
+ <Library Value="ssl"/>
+ <Library Value="crypto"/>
+ <Library Value="dl"/>
+ <Library Value="rt"/>
+ <Library Value="png"/>
+ <Library Value="z"/>
+ <Library Value="unwind"/>
+ <Library Value="unwind-x86_64"/>
+ </Linker>
+ <ResourceCompiler Options=""/>
+ </GlobalSettings>
+ <Configuration Name="Debug" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Executable" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-g;-O0;-Wall;-DDEBUG;-fsanitize=address" C_Options="-pg;-g;-O0;-Wall;-DDEBUG;-fsanitize=address" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0"/>
+ <Linker Options="-pg;-fsanitize=address" Required="yes"/>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName)" IntermediateDirectory="./Debug" Command="./$(ProjectName)" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="no">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName>None</ThirdPartyToolName>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ <Configuration Name="Release" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Executable" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-O2;-Wall" C_Options="-O2;-std=c99;-Wall;-fno-strict-aliasing;-pthread;-D_POSIX_C_SOURCE=200112L;-D_XOPEN_SOURCE=500;-D_GNU_SOURCE" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0">
+ <IncludePath Value="."/>
+ <IncludePath Value="../../3rdparty/json.h"/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../rdp/include"/>
+ <IncludePath Value="../../3rdparty/wslay/lib/includes"/>
+ <IncludePath Value="../../3rdparty/wslay/build/lib/includes"/>
+ <IncludePath Value="../../3rdparty/libev"/>
+ <IncludePath Value="../../3rdparty/picohttpparser"/>
+ <IncludePath Value="../../3rdparty/libcb/include"/>
+ <IncludePath Value="../../3rdparty/curl/build/lib"/>
+ <IncludePath Value="../../3rdparty/curl/include"/>
+ <Preprocessor Value="NDEBUG"/>
+ </Compiler>
+ <Linker Options="-pthread;../rdp/Release/rdp.a;../../3rdparty/libev/build/libev.a;../../3rdparty/wslay/build/lib/libwslay.a;../../3rdparty/FreeRDP/build/client/common/libfreerdp-client2.a;../../3rdparty/FreeRDP/build/channels/geometry/client/libgeometry-client.a;../../3rdparty/FreeRDP/build/channels/rdpgfx/client/librdpgfx-client.a;../../3rdparty/FreeRDP/build/channels/video/client/libvideo-client.a;../../3rdparty/FreeRDP/build/channels/parallel/client/libparallel-client.a;../../3rdparty/FreeRDP/build/channels/drive/client/libdrive-client.a;../../3rdparty/FreeRDP/build/channels/cliprdr/client/libcliprdr-client.a;../../3rdparty/FreeRDP/build/channels/drdynvc/client/libdrdynvc-client.a;../../3rdparty/FreeRDP/build/channels/tsmf/client/libtsmf-client.a;../../3rdparty/FreeRDP/build/channels/remdesk/client/libremdesk-client.a;../../3rdparty/FreeRDP/build/channels/rdpdr/client/librdpdr-client.a;../../3rdparty/FreeRDP/build/channels/rdpei/client/librdpei-client.a;../../3rdparty/FreeRDP/build/channels/encomsp/client/libencomsp-client.a;../../3rdparty/FreeRDP/build/channels/audin/client/libaudin-client.a;../../3rdparty/FreeRDP/build/channels/disp/client/libdisp-client.a;../../3rdparty/FreeRDP/build/channels/echo/client/libecho-client.a;../../3rdparty/FreeRDP/build/channels/rail/client/librail-client.a;../../3rdparty/FreeRDP/build/channels/serial/client/libserial-client.a;../../3rdparty/FreeRDP/build/channels/rdpsnd/client/librdpsnd-client.a;../../3rdparty/FreeRDP/build/channels/rdpsnd/client/fake/librdpsnd-client-fake.a;../../3rdparty/FreeRDP/build/channels/smartcard/client/libsmartcard-client.a;../../3rdparty/FreeRDP/build/libfreerdp/libfreerdp2.a;../../3rdparty/FreeRDP/build/winpr/libwinpr/libwinpr2.a;../../3rdparty/curl/build/libcurl.a" Required="yes">
+ <Library Value="ssl"/>
+ <Library Value="crypto"/>
+ <Library Value="dl"/>
+ <Library Value="rt"/>
+ <Library Value="png"/>
+ <Library Value="z"/>
+ </Linker>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName)" IntermediateDirectory="./Release" Command="./$(ProjectName)" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="no">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName>None</ThirdPartyToolName>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ </Settings>
+</CodeLite_Project>
diff --git a/src/core/ctl_task.h b/src/core/ctl_task.h
new file mode 100644
index 0000000..7535abd
--- /dev/null
+++ b/src/core/ctl_task.h
@@ -0,0 +1,18 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+typedef struct ctl_task_info_s
+{
+
+ /* control session instance */
+ ctl_session *session;
+
+ /* pointer to wrdp_thpool_task* used by some internals */
+ void *wrdp_thpool_task;
+
+} ctl_task_info;
diff --git a/src/core/curl_helpers.c b/src/core/curl_helpers.c
new file mode 100644
index 0000000..b24ce51
--- /dev/null
+++ b/src/core/curl_helpers.c
@@ -0,0 +1,864 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+#include <curl/curl.h>
+#include <wslay/wslay.h>
+
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+
+#include "globals.h"
+#include "curl_helpers.h"
+#include "ws_protocol.h"
+#include "wrdp_thpool.h"
+#include "webrdp_module_api.h"
+#include "ws_session.h"
+#include "task.h"
+#include "thread_impl.h"
+#include "exports.h"
+
+#include "webrdp_core_api.h"
+#include "log.h"
+
+/* global curl code */
+
+/* TODO: per thread watcher list */
+
+struct socket_watcher_s;
+typedef struct socket_watcher_s socket_watcher;
+
+struct socket_watcher_s
+{
+ ev_io *watcher;
+ int fd, what;
+ wrdp_thpool_thread *t;
+ CURL *easy_handle;
+ socket_watcher *prev, *next;
+};
+
+typedef struct
+{
+ CURLM *cm;
+ socket_watcher *watcher_list;
+} curl_thread_data;
+
+static socket_watcher *
+find_socket_watcher(int fd, int what, socket_watcher **head)
+{
+ socket_watcher *w = *head;
+ while (w)
+ {
+ if (w->fd == fd)
+ {
+ if (what != -1 && w->what == what)
+ {
+ return w;
+ }
+ }
+ w = w->next;
+ }
+ return 0;
+}
+
+static socket_watcher *
+find_watcher_by_handle(CURL *easy_handle, socket_watcher **head)
+{
+ socket_watcher *w = *head;
+ while (w)
+ {
+ if (w->easy_handle == easy_handle)
+ {
+ return w;
+ }
+ w = w->next;
+ }
+ return 0;
+}
+
+static void
+append_socket_watcher(socket_watcher *_w, socket_watcher **head)
+{
+ _w->next = *head;
+ if (*head)
+ {
+ (*head)->prev = _w;
+ }
+ *head = _w;
+}
+
+typedef struct
+{
+ CURLM *multi;
+ wrdp_thpool_thread *t;
+} curl_socket_data;
+
+typedef struct
+{
+ curl_socket_data *csd;
+ int fd;
+} c_f_i_cb_data;
+
+static void
+remove_socket_watcher_impl(socket_watcher *w, socket_watcher **head)
+{
+ if (w->prev)
+ {
+ w->prev->next = w->next;
+ }
+ if (w->next)
+ {
+ w->next->prev = w->prev;
+ }
+ if (w == *head)
+ {
+ *head = w->next;
+ }
+ if (w->watcher)
+ {
+ if (ev_is_active(w->watcher))
+ {
+ ev_io_stop(w->t->ev_th_loop, w->watcher);
+ }
+ if (w->watcher->data)
+ {
+ free(w->watcher->data);
+ }
+ free(w->watcher);
+ }
+ free(w);
+}
+
+static void
+remove_socket_watcher(int fd, int what, socket_watcher **head)
+{
+ socket_watcher *w = 0;
+ for (w = find_socket_watcher(fd, what, head); w;
+ w = find_socket_watcher(fd, what, head))
+ {
+ remove_socket_watcher_impl(w, head);
+ }
+}
+
+static void
+remove_all_watchers(CURL *easy_handle, socket_watcher **head)
+{
+ socket_watcher *w = 0;
+ for (w = find_watcher_by_handle(easy_handle, head); w;
+ w = find_watcher_by_handle(easy_handle, head))
+ {
+ remove_socket_watcher_impl(w, head);
+ }
+}
+
+static void
+curl_check_finished(CURLM *cm)
+{
+ CURLMsg *msg = 0;
+ int finished = 0;
+ while ((msg = curl_multi_info_read(cm, &finished)))
+ {
+ {
+ const char *msg = "curl transfer finished";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ {
+ curl_request_info *cri = 0;
+ if (msg->msg != CURLMSG_DONE)
+ {
+ //TODO: handle errors
+ continue;
+ }
+ curl_easy_getinfo(
+ msg->easy_handle, CURLINFO_PRIVATE, &cri);
+ if (!cri)
+ {
+ continue;
+ }
+ curl_multi_remove_handle(cm, msg->easy_handle);
+ ws_session *t = cri->session;
+ task_info *info = t->task_info;
+ wrdp_thpool_task *task = 0;
+ if (info)
+ task = info->wrdp_thpool_task;
+ if (task)
+ {
+ curl_thread_data *ctd
+ = task->thread->pool->userdata;
+ remove_all_watchers(msg->easy_handle,
+ &(ctd[task->thread->thread_id]
+ .watcher_list));
+ }
+ if (!cri->data)
+ {
+ continue;
+ }
+ if (info && info->stopped)
+ {
+ goto clean;
+ }
+ if (msg->data.result != CURLE_OK)
+ {
+ char msg_str[64], log_str[64];
+ log_msg_info i = {0};
+ snprintf(log_str, 63,
+ "curl transfer failed"
+ " with error:\n\t%s",
+ curl_easy_strerror(msg->data.result));
+ i.buf = (uint8_t *)log_str;
+ i.task_info = info;
+ i.ws_session = t;
+ i.level = wrdp_log_level_error;
+ log_msg_ex(&i);
+ snprintf(msg_str, 63, "curl_transfer_error_%d",
+ (int)(msg->data.result));
+ send_error_msg(msg_str, info);
+ t->session_state = ws_session_error;
+ goto clean;
+ }
+ cri->data[cri->data_size - 1] = 0;
+#ifdef DEBUG
+ /* print server request/reply data */
+ {
+ size_t buf_size = cri->data_size + 256;
+ char buf[buf_size];
+ log_msg_info i = {0};
+ i.task_info = info;
+ i.ws_session = t;
+ snprintf(buf, buf_size - 1,
+ "curl raw data:\nrequest type: %s\ndata:\n "
+ "%.*s",
+ cri->type == curl_request_type_get ? "GET" :
+ "POST",
+ (int)cri->data_size, cri->data);
+ i.buf = (uint8_t *)buf;
+ i.level = wrdp_log_level_trace;
+ log_msg_ex(&i);
+ }
+#endif
+
+ if (cri->user_data_handler_cb)
+ {
+ if (!cri->user_data_handler_cb(
+ cri->data, cri->session, cri->userdata))
+ {
+ cri->session->session_state
+ = ws_session_error;
+ task_destroy_client_connection(
+ cri->session);
+ }
+ }
+ clean:
+ if (!SLIST_EMPTY(&(t->curls_easy_head)))
+ {
+ struct curls_easy_s *curl_node
+ = SLIST_FIRST(&(t->curls_easy_head));
+ while (curl_node)
+ {
+ CURL *curl = curl_node->curl;
+ struct curls_easy_s *node = curl_node;
+
+ curl_node
+ = SLIST_NEXT(curl_node, entries);
+ if (curl == msg->easy_handle)
+ {
+ SLIST_REMOVE(
+ &t->curls_easy_head, node,
+ curls_easy_s, entries);
+ free(node);
+ }
+ }
+ }
+ if (cri->free_cb)
+ cri->free_cb(cri->userdata);
+ free(cri->data);
+ cri->data = 0;
+ free(cri);
+ curl_easy_cleanup(msg->easy_handle);
+ {
+ log_msg(
+ (const uint8_t
+ *)"clearing finished curl transfer",
+ 0, wrdp_log_level_trace, 0);
+ }
+ }
+ }
+}
+
+void
+curl_list_session_destroy(ws_session *session)
+{
+ CURLM *cm = session->curlm;
+ if (!SLIST_EMPTY(&(session->curls_easy_head)))
+ {
+ struct curls_easy_s *curl_node
+ = SLIST_FIRST(&(session->curls_easy_head));
+ while (curl_node)
+ {
+ CURL *curl = curl_node->curl;
+
+ curl_request_info *cri = 0;
+ curl_easy_getinfo(curl, CURLINFO_PRIVATE, &cri);
+ {
+ struct curls_easy_s *node = curl_node;
+
+ curl_node = SLIST_NEXT(curl_node, entries);
+ SLIST_REMOVE(&(session->curls_easy_head), node,
+ curls_easy_s, entries);
+ free(node);
+ }
+ if (!cri)
+ {
+ curl_multi_remove_handle(cm, curl);
+ curl_easy_cleanup(curl);
+ continue;
+ }
+ curl_multi_remove_handle(cm, curl);
+ curl_easy_cleanup(curl);
+ if (!cri->data)
+ {
+ continue;
+ }
+ free(cri->data);
+ free(cri);
+ }
+ }
+}
+
+static void
+curl_fd_io_cb(EV_P_ ev_io *w, int revents)
+{
+ c_f_i_cb_data *d = w->data;
+ int pending = 0;
+ int ev_bitmask = 0;
+ if (revents & EV_READ && revents & EV_WRITE)
+ {
+ ev_bitmask |= CURL_POLL_INOUT;
+ }
+ else if (revents & EV_READ)
+ {
+ ev_bitmask |= CURL_POLL_IN;
+ }
+ else if (revents & EV_WRITE)
+ {
+ ev_bitmask |= CURL_POLL_OUT;
+ }
+ CURLMcode code = curl_multi_socket_action(
+ d->csd->multi, d->fd, ev_bitmask, &pending);
+ if (code != CURLM_OK)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_multi_socket_action failed with error: %d", code);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+ curl_check_finished(d->csd->multi);
+}
+
+static int
+curl_socket_cb(CURL *easy, /* easy handle */
+ curl_socket_t s, /* socket */
+ int what, /* describes the socket */
+ void *userp, /* private callback pointer */
+ void *socketp) /* private socket pointer */
+{
+ curl_socket_data *csd = userp;
+ if (what == CURL_POLL_REMOVE)
+ {
+ {
+ curl_thread_data *ctd = csd->t->pool->userdata;
+ remove_socket_watcher(
+ s, -1, &(ctd[csd->t->thread_id].watcher_list));
+ }
+ goto finish;
+ }
+ socket_watcher *sw = calloc(1, sizeof(socket_watcher));
+ if (!sw)
+ {
+ perror("calloc");
+ goto finish;
+ }
+ ev_io *w = calloc(1, sizeof(ev_io));
+ if (!w)
+ {
+ free(sw);
+ perror("calloc");
+ goto finish;
+ }
+ c_f_i_cb_data *d = calloc(1, sizeof(c_f_i_cb_data));
+ if (!d)
+ {
+ free(sw);
+ free(w);
+ perror("calloc");
+ goto finish;
+ }
+ d->csd = csd;
+ d->fd = s;
+ sw->t = csd->t;
+ sw->watcher = w;
+ sw->fd = s;
+ sw->what = what;
+ sw->easy_handle = easy;
+ w->data = d;
+ {
+ curl_thread_data *ctd = csd->t->pool->userdata;
+ append_socket_watcher(
+ sw, &(ctd[csd->t->thread_id].watcher_list));
+ }
+ if (what == CURL_POLL_IN)
+ {
+ ev_io_init(w, curl_fd_io_cb, s, EV_READ);
+ }
+ else if (what == CURL_POLL_OUT)
+ {
+ ev_io_init(w, curl_fd_io_cb, s, EV_WRITE);
+ }
+ else if (what == CURL_POLL_INOUT)
+ {
+ ev_io_init(w, curl_fd_io_cb, s, EV_READ | EV_WRITE);
+ }
+ ev_io_start(csd->t->ev_th_loop, w);
+finish:
+ curl_check_finished(csd->multi);
+ return CURLM_OK;
+}
+
+static void
+timeouts_check_cb(EV_P_ ev_timer *w, int revents)
+{
+ /* TODO: test this */
+ CURLM *multi = w->data;
+ int handles = 0;
+ free(w);
+ curl_check_finished(multi);
+ CURLMcode code
+ = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &handles);
+ if (code != CURLM_OK)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_multi_socket_action failed with error: %d", code);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+}
+
+static int
+curl_timer_cb(CURLM *multi, long timeout_ms, void *userp)
+{
+ if (timeout_ms > 0)
+ {
+ int handles = 0;
+ CURLMcode code = curl_multi_socket_action(
+ multi, CURL_SOCKET_TIMEOUT, 0, &handles);
+ if (code != CURLM_OK)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_multi_socket_action failed with error: %d",
+ code);
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ }
+ return 0;
+ }
+ wrdp_thpool_thread *t = userp;
+ ev_timer *timer = calloc(1, sizeof(ev_timer));
+ if (!timer)
+ {
+ perror("calloc");
+ return -1;
+ }
+ ev_timer_init(timer, timeouts_check_cb, timeout_ms / 1000.0, 0.);
+ timer->data = multi;
+ ev_timer_start(t->ev_th_loop, timer);
+ return 0;
+}
+
+void
+per_thread_curl_init(void *user_pool_data, wrdp_thpool_thread *t)
+{
+ curl_thread_data *ctd = user_pool_data;
+ ctd[t->thread_id].cm = curl_multi_init();
+ if (!ctd[t->thread_id].cm)
+ {
+ printf("FATAL: curl_multi_init failed\n");
+ exit(EXIT_FAILURE);
+ }
+ curl_socket_data *csd = calloc(1, sizeof(curl_socket_data));
+ if (!csd)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ csd->multi = ctd[t->thread_id].cm;
+ csd->t = t;
+ if (curl_multi_setopt(
+ ctd[t->thread_id].cm, CURLMOPT_SOCKETFUNCTION, curl_socket_cb)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id],"
+ "CURLMOPT_SOCKETFUNCTION, curl_socket_cb) failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (curl_multi_setopt(ctd[t->thread_id].cm, CURLMOPT_SOCKETDATA, csd)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id], "
+ "CURLMOPT_SOCKETDATA,"
+ " csd) failed failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (curl_multi_setopt(
+ ctd[t->thread_id].cm, CURLMOPT_TIMERFUNCTION, curl_timer_cb)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id], "
+ "CURLMOPT_TIMERFUNCTION,"
+ "curl_timer_cb) failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (curl_multi_setopt(ctd[t->thread_id].cm, CURLMOPT_TIMERDATA, t)
+ != CURLM_OK)
+ {
+ printf("curl_multi_setopt(curlms[t->thread_id], "
+ "CURLMOPT_TIMERDATA,"
+ " t) failed\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+void *
+init_curl_global()
+{
+ curl_thread_data *ret;
+ CURLcode cc = curl_global_init(CURL_GLOBAL_ALL | CURL_GLOBAL_ACK_EINTR);
+ if (cc != CURLE_OK)
+ {
+ printf("FATAL: curl_global_init failed\n");
+ exit(EXIT_FAILURE);
+ }
+ ret = calloc(g_globals.settings.thread_count, sizeof(curl_thread_data));
+ if (!ret)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ return ret;
+}
+
+/* transfer related code */
+
+static size_t
+curl_read_cb(char *buffer, size_t size, size_t nitems, void *userdata)
+{
+ curl_request_info *cri = userdata;
+ if (!cri || !cri->data)
+ {
+ return 0;
+ }
+ ws_session *t = cri->session;
+ task_info *info = t->task_info;
+ if (info && info->stopped)
+ return 0;
+ size_t len = size * nitems;
+ if (len > (cri->data_size - cri->written - 1))
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "curl_read_cb: error: buffer"
+ " of size %ld is too small\n",
+ cri->data_size);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ if (!len)
+ {
+ const char *msg = "curl_read_cb: error: zero length reply";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ memcpy(cri->data + cri->written, buffer, len);
+ cri->data[len] = 0;
+ cri->written += len;
+ curl_check_finished(cri->cm);
+ return len;
+error:
+ if (cri->free_cb)
+ {
+ cri->free_cb(cri->userdata);
+ }
+ free(cri->data);
+ free(cri);
+ return 0;
+}
+
+char *
+get_url(const char *token, curl_request_type type)
+{
+ switch (type)
+ {
+ case curl_request_type_get:
+ {
+ size_t len
+ = strlen(token)
+ + strlen(g_globals.settings.auth_server_url)
+ + strlen("?") + 1;
+ char *url = malloc(len);
+ if (!url)
+ {
+ perror("malloc");
+ return NULL;
+ }
+ snprintf(url, len, "%s?%s",
+ g_globals.settings.auth_server_url, token);
+ return url;
+ }
+ break;
+ case curl_request_type_post:
+ {
+ char *url = malloc(
+ strlen(g_globals.settings.auth_server_url) + 1);
+ if (!url)
+ {
+ perror("malloc");
+ return NULL;
+ }
+ strcpy(url, g_globals.settings.auth_server_url);
+ return url;
+ }
+ break;
+ default:
+ return NULL;
+ }
+}
+
+curl_request_info *
+curl_init_request(ws_session *session, curl_request_type type, uint8_t *data,
+ size_t data_size,
+ bool (*user_data_handler_cb)(uint8_t *, ws_session *, void *),
+ void (*free_cb)(void *), void *userdata)
+{
+ curl_request_info *request = calloc(1, sizeof(*request));
+ request->type = type;
+ request->session = session;
+ if (type == curl_request_type_get)
+ {
+ request->data_size = 4096;
+ request->data = malloc(request->data_size);
+ request->data[0] = 0;
+ }
+ if (type == curl_request_type_post)
+ {
+ request->data_size = data_size;
+ request->data = malloc(request->data_size + 1);
+ memcpy(request->data, data, data_size);
+ request->data[data_size] = 0;
+ }
+ request->url = get_url(session->token_base64, type);
+ request->user_data_handler_cb = user_data_handler_cb;
+ request->free_cb = free_cb;
+ request->userdata = userdata;
+
+ return request;
+}
+
+bool
+curl_request(curl_request_info *r)
+{
+ CURL *c = 0;
+ curl_thread_data *ctd = 0;
+ CURLM *cm = 0;
+ ws_session *session = 0;
+ task_info *t_info = 0;
+ wrdp_thpool_task *task = 0;
+ if (!r)
+ return false;
+ session = r->session;
+ if (!session)
+ {
+ return false;
+ }
+ t_info = session->task_info;
+ if (!t_info)
+ {
+ return false;
+ }
+ task = t_info->wrdp_thpool_task;
+ if (!task)
+ {
+ return false;
+ }
+ ctd = task->thread->pool->userdata;
+ cm = ctd[task->thread->thread_id].cm;
+ r->cm = cm;
+ session->curlm = cm;
+ c = curl_easy_init();
+ if (!r->url || !r->data)
+ {
+ goto curl_easy_setopt_error;
+ }
+ if (!c)
+ {
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(c, CURLOPT_URL, r->url) != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_URL, url) failed";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ switch (r->type)
+ {
+ case curl_request_type_get:
+ if (curl_easy_setopt(c, CURLOPT_WRITEDATA, r)
+ != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(
+ c, CURLOPT_WRITEFUNCTION, curl_read_cb)
+ != CURLE_OK)
+ {
+ const char *msg = "curl_easy_setopt (c, "
+ "CURLOPT_WRITEFUNCTION,"
+ " curl_read_cb) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ break;
+ case curl_request_type_post:
+ if (curl_easy_setopt(c, CURLOPT_POST, 1L) != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_POST,"
+ " 1L) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(
+ c, CURLOPT_POSTFIELDSIZE, r->data_size)
+ != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ if (curl_easy_setopt(c, CURLOPT_POSTFIELDS, r->data)
+ != CURLE_OK)
+ {
+ const char *msg
+ = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ {
+ log_msg_info i = {0};
+ size_t buf_size = r->data_size + 256;
+ uint8_t buf[buf_size];
+ i.task_info = t_info;
+ i.ws_session = session;
+ i.level = wrdp_log_level_trace;
+ snprintf((char *)buf, buf_size - 1,
+ "curl postfields data: %.*s",
+ (int)r->data_size, r->data);
+ i.buf = buf;
+ log_msg_ex(&i);
+ }
+ break;
+ default:
+ {
+ const char *msg = "request type is wrong";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+ break;
+ }
+ if (curl_easy_setopt(c, CURLOPT_PRIVATE, r) != CURLE_OK)
+ {
+ const char *msg = "curl_easy_setopt (c, CURLOPT_WRITEDATA,"
+ " crd) failed";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto curl_easy_setopt_error;
+ }
+#ifdef DEBUG
+ curl_easy_setopt(c, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(c, CURLOPT_SSL_VERIFYHOST, 0);
+#endif
+ if (curl_multi_add_handle(cm, c) != CURLM_OK)
+ {
+ const char *msg = "curl_multi_add_handle failed";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ free(r->url);
+ return false;
+ }
+
+ {
+ struct curls_easy_s *entry
+ = calloc(1, sizeof(struct curls_easy_s));
+ if (!entry)
+ {
+ perror("calloc");
+ goto curl_easy_setopt_error;
+ }
+ entry->session = session;
+ entry->curl = c;
+
+ SLIST_INSERT_HEAD(&(session->curls_easy_head), entry, entries);
+ }
+
+ free(r->url);
+ return true;
+curl_easy_setopt_error:
+ if (r->free_cb)
+ r->free_cb(r->userdata);
+ free(r->url);
+ free(r->data);
+ free(r);
+ return false;
+}
+
+size_t
+curl_prepare_post_request_data(uint8_t **buf, ws_session *session)
+{
+ size_t data_size = strlen("{\"sid\": \"%s\", \"type\": "
+ "\"sessionupdate\", \"status\": %d}")
+ - 4 + //format
+ strlen(session->token_base64)
+ + 1; //one digit (session_state)
+ uint8_t *data = malloc(data_size + 1);
+ sprintf((char *)data,
+ "{\"sid\": \"%s\", \"type\": \"sessionupdate\", \"status\": %d}",
+ session->token_base64, session->session_state);
+ *buf = data;
+ return data_size;
+}
diff --git a/src/core/curl_helpers.h b/src/core/curl_helpers.h
new file mode 100644
index 0000000..e1f4205
--- /dev/null
+++ b/src/core/curl_helpers.h
@@ -0,0 +1,76 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+
+#pragma once
+
+void *init_curl_global();
+
+void per_thread_curl_init(void *user_pool_data, wrdp_thpool_thread *t);
+
+bool curl_contact_auth_server(const char *token, void *user_data);
+
+typedef enum curl_request_type_
+{
+ curl_request_type_get,
+ curl_request_type_post
+} curl_request_type;
+
+typedef struct _request_info
+{
+ /* CURLM */
+ void *cm;
+
+ /* type of HTTP request */
+ curl_request_type type;
+ ws_session *session;
+
+ /* buffer to be written (GET) / to be sent (POST) */
+ uint8_t *data;
+
+ /* capacity of data */
+ size_t data_size;
+
+ /* size of handled data */
+ size_t written;
+
+ /* URL to use in the request; must be URL-encoded in the following
+ * format: scheme://host:port/path */
+ char *url;
+
+ /* handler callback
+ * may be NULL
+ * return false on failure */
+ bool (*user_data_handler_cb)(
+ uint8_t *buf, ws_session *session, void *userdata);
+
+ /* callback to clean up userdata */
+ void (*free_cb)(void *userdata);
+
+ /* user specified data passed to all callbacks */
+ void *userdata;
+} curl_request_info;
+
+curl_request_info *curl_init_request(ws_session *session,
+ curl_request_type type, uint8_t *data, size_t data_size,
+ bool (*user_data_handler_cb)(
+ uint8_t *buf, ws_session *session, void *userdata),
+ void (*free_cb)(void *userdata), void *userdata);
+
+bool curl_request(curl_request_info *r);
+
+char *get_url(const char *s, curl_request_type type);
+
+void curl_list_session_destroy(ws_session *session);
+
+struct curls_easy_s
+{
+ ws_session *session;
+ void *curl;
+ SLIST_ENTRY(curls_easy_s) entries;
+};
+
+size_t curl_prepare_post_request_data(uint8_t **buf, ws_session *session);
diff --git a/src/core/ev_loop.c b/src/core/ev_loop.c
new file mode 100644
index 0000000..e084e81
--- /dev/null
+++ b/src/core/ev_loop.c
@@ -0,0 +1,206 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include <webrdp_module_api.h>
+
+#include "globals.h"
+#include "remote_control.h"
+#include "thread_impl.h"
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "ws_session.h"
+#include "socket_helpers.h"
+#include "task.h"
+#include "remote_control.h"
+
+#include "log.h"
+
+static ws_session *
+ws_create_session(int connection_fd)
+{
+ ws_session *session = 0;
+ task_info *info = 0;
+ session = calloc(1, sizeof(ws_session));
+ if (!session)
+ {
+ perror("calloc");
+ goto error;
+ }
+ info = calloc(1, sizeof(task_info));
+ if (!info)
+ {
+ perror("calloc");
+ goto error;
+ }
+ session->task_info = info;
+ session->session_state = ws_session_initial;
+ session->connection_fd = connection_fd;
+ SLIST_INIT(&(session->backend_settings_head));
+ return session;
+error:
+ if (session)
+ {
+ if (session->task_info)
+ {
+ free(session->task_info);
+ }
+ free(session);
+ }
+ return 0;
+}
+
+static void
+ws_server_cb(EV_P_ ev_io *w, int revents)
+{
+ int *sock = w->data;
+ int con = accept_new_connection(*sock);
+ if (con != -1)
+ {
+ ws_session *session = 0;
+ {
+ const char *msg = "accepting new ws connection";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ session = ws_create_session(con);
+ if (!session)
+ {
+ close(con);
+ return;
+ }
+ if (!wrdp_thread_pool_add_task(g_globals.thpool, ws_run_session,
+ ws_session_init_cb, session))
+ {
+ const char *msg
+ = "Failed to move task to pool: pool is full";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return;
+ }
+ }
+}
+
+static void
+ctl_server_cb(EV_P_ ev_io *w, int revents)
+{
+ int *sock = w->data;
+ int con = accept_new_connection(*sock);
+ if (con != -1)
+ {
+ const char *msg = "accepting new ctl connection";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ ctl_task_info *info = ctl_create_task(con);
+ if (!info)
+ {
+ close(con);
+ return;
+ }
+ if (!wrdp_thread_pool_add_task(
+ g_globals.thpool, ctl_run_task, 0, info))
+ {
+ const char *msg
+ = "Failed to move task to pool: pool is full";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return;
+ }
+ /* TODO: */
+ }
+}
+
+void
+run_ev_loop_main(int ws_tcp_sock, int ws_unix_sock, int ctl_server_sock_tcp,
+ int ctl_server_sock_unix)
+{
+ if (ws_tcp_sock == -1 && ws_unix_sock == -1)
+ {
+ const char *msg
+ = "ws: both tcp port and unix socket does not set";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ if (ctl_server_sock_tcp == -1 && ctl_server_sock_unix == -1)
+ {
+ const char *msg
+ = "ctl: both tcp port and unix socket does not set";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ struct ev_loop *ev_loop = EV_DEFAULT;
+ ev_io *ws_ev_sock_acceptor, *ws_ev_unix_acceptor, *ctl_ev_sock_acceptor,
+ *ctl_ev_unix_acceptor;
+ if (ws_tcp_sock != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ws_ev_sock_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ws_tcp_sock;
+ ev_io_init(
+ ws_ev_sock_acceptor, ws_server_cb, ws_tcp_sock, EV_READ);
+ ws_ev_sock_acceptor->data = sock;
+ ev_io_start(ev_loop, ws_ev_sock_acceptor);
+ }
+ if (ws_unix_sock != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ws_ev_unix_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ws_unix_sock;
+ ev_io_init(
+ ws_ev_unix_acceptor, ws_server_cb, ws_unix_sock, EV_READ);
+ ws_ev_unix_acceptor->data = sock;
+ ev_io_start(ev_loop, ws_ev_unix_acceptor);
+ }
+ if (ctl_server_sock_tcp != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ctl_ev_sock_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ctl_server_sock_tcp;
+ ev_io_init(ctl_ev_sock_acceptor, ctl_server_cb,
+ ctl_server_sock_tcp, EV_READ);
+ ctl_ev_sock_acceptor->data = sock;
+ ev_io_start(ev_loop, ctl_ev_sock_acceptor);
+ }
+ if (ctl_server_sock_unix != -1)
+ {
+ int *sock = calloc(1, sizeof(int));
+ if (!sock)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ ctl_ev_unix_acceptor = calloc(1, sizeof(ev_io));
+ *sock = ctl_server_sock_unix;
+ ev_io_init(ctl_ev_unix_acceptor, ctl_server_cb,
+ ctl_server_sock_unix, EV_READ);
+ ctl_ev_unix_acceptor->data = sock;
+ ev_io_start(ev_loop, ctl_ev_unix_acceptor);
+ }
+ ev_run(ev_loop, 0);
+}
diff --git a/src/core/ev_loop.h b/src/core/ev_loop.h
new file mode 100644
index 0000000..5be49c4
--- /dev/null
+++ b/src/core/ev_loop.h
@@ -0,0 +1,11 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+
+#pragma once
+
+void run_ev_loop_main(int ws_tcp_sock, int ws_unix_sock,
+ int ctl_server_sock_tcp, int ctl_server_sock_unix);
diff --git a/src/core/exports.c b/src/core/exports.c
new file mode 100644
index 0000000..2f336be
--- /dev/null
+++ b/src/core/exports.c
@@ -0,0 +1,396 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <ev.h>
+#include <wslay/wslay.h>
+
+#include "ws_session.h"
+#include "ws_protocol.h"
+#include "webrdp_module_api.h"
+#include "wrdp_thpool_internals.h"
+#include "globals.h"
+#include "task.h"
+
+#include "thread_impl.h"
+
+#include "utilities.h"
+#include "log.h"
+
+void
+send_text_msg(const char *msg, void *_task_info)
+{
+ ws_send_text((uint8_t *)msg, strlen(msg), _task_info);
+}
+
+static void
+send_typed_text_msg(const char *type, const char *msg, void *_task_info)
+{
+ size_t len = strlen(msg) + strlen(type);
+ uint8_t *buf = malloc(len);
+#ifdef DEBUG
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+#endif
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memset(buf, 0, len);
+ memcpy(buf, type, strlen(type));
+ memcpy(buf + strlen(type), msg, len - strlen(type));
+ ws_send_text(buf, len, _task_info);
+ free(buf);
+}
+
+void
+send_text_info_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("I:", msg, task_info);
+}
+
+void
+send_text_warning_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("W:", msg, task_info);
+}
+
+void
+send_text_debug_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("D:", msg, task_info);
+}
+
+void
+send_error_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("E:", msg, task_info);
+}
+
+void
+send_ctl_msg(const char *msg, void *task_info)
+{
+ send_typed_text_msg("0:", msg, task_info);
+}
+
+static void
+send_termination_msg(void *task_info)
+{
+ ws_send_text((uint8_t *)"T:", 2, task_info);
+}
+
+static void
+send_typed_proto_msg(
+ uint8_t *buf, size_t size, ws_output_codes type, void *task_info)
+{
+ uint8_t *msg = ws_pack_msg(buf, size, type);
+ ws_send_binary(msg, size + 4, task_info);
+ free(msg);
+}
+
+static void
+send_begin_paint(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_beginpaint, task_info);
+}
+
+static void
+send_end_paint(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_endpaint, task_info);
+}
+
+static void
+send_bitmap(
+ const wrdp_core_display_bmp *bmp, uint8_t *bmp_data, void *task_info)
+{
+ size_t buf_size = sizeof(wrdp_core_display_bmp) + bmp->size;
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, bmp, sizeof(wrdp_core_display_bmp));
+ memcpy(buf + sizeof(wrdp_core_display_bmp), bmp_data, bmp->size);
+ send_typed_proto_msg(buf, buf_size, ws_out_bitmap, task_info);
+ free(buf);
+}
+
+static void
+send_opaque_rect_order(
+ const wrdp_core_display_opaque_rect_order *oro, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)oro,
+ sizeof(wrdp_core_display_opaque_rect_order), ws_out_opaquerect,
+ task_info);
+}
+
+static void
+send_set_bounds(const wrdp_core_display_bounds *bounds, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)bounds,
+ sizeof(wrdp_core_display_bounds), ws_out_setbounds, task_info);
+}
+
+static void
+send_pat_blt(const wrdp_core_display_patblt_order *patblt, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)patblt,
+ sizeof(wrdp_core_display_patblt_order), ws_out_patblt, task_info);
+}
+
+static void
+send_multi_opaque_rect(
+ const wrdp_core_display_m_opaque_rect *mrect, void *task_info)
+{
+ size_t buf_size
+ = (sizeof(uint32_t) * 2)
+ + (sizeof(wrdp_core_display_delta_rect) * mrect->num_rect);
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, (uint8_t *)&(mrect->color), 4);
+ memcpy(buf + 4, (uint8_t *)&(mrect->num_rect), 4);
+ memcpy(buf + 4 + 4, (uint8_t *)mrect->rects,
+ mrect->num_rect * sizeof(wrdp_core_display_delta_rect));
+
+ send_typed_proto_msg(buf, buf_size, ws_out_multi_opaquerect, task_info);
+ free(buf);
+}
+
+static void
+send_scr_blt(const wrdp_core_display_scr_blt *scr_blt, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)&scr_blt,
+ sizeof(wrdp_core_display_scr_blt), ws_out_scr_btl, task_info);
+}
+
+static void
+send_ptr_new(const wrdp_core_display_cursor_info *ptr, void *task_info)
+{
+ size_t buf_size = sizeof(wrdp_core_display_cursor) - sizeof(uint8_t *)
+ + sizeof(wrdp_core_display_cursor_info)
+ - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *) + ptr->data_size;
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, ptr,
+ sizeof(wrdp_core_display_cursor_info) - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *));
+ memcpy(buf + sizeof(wrdp_core_display_cursor_info) - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *),
+ ptr->cur, sizeof(wrdp_core_display_cursor) - sizeof(uint8_t *));
+ memcpy(buf + sizeof(wrdp_core_display_cursor_info) - sizeof(uint32_t)
+ - sizeof(wrdp_core_display_cursor *)
+ + sizeof(wrdp_core_display_cursor) - sizeof(uint8_t *),
+ ptr->cur->data, ptr->data_size);
+ send_typed_proto_msg(buf, buf_size, ws_out_ptr_new, task_info);
+ free(buf);
+}
+
+static void
+send_ptr_free(uint32_t ptr_id, void *task_info)
+{
+ send_typed_proto_msg(
+ (uint8_t *)&ptr_id, sizeof(uint32_t), ws_out_ptr_free, task_info);
+}
+
+static void
+send_ptr_set(uint32_t ptr_id, void *task_info)
+{
+ send_typed_proto_msg(
+ (uint8_t *)&ptr_id, sizeof(uint32_t), ws_out_ptr_set, task_info);
+}
+
+static void
+send_ptr_set_null(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_ptr_set_null, task_info);
+}
+
+static void
+send_ptr_set_default(void *task_info)
+{
+ send_typed_proto_msg(0, 0, ws_out_ptr_set_default, task_info);
+}
+
+static void
+clipboard_changed(uint8_t *fmts, size_t fmts_count, void *task_info)
+{
+ send_typed_proto_msg(
+ fmts, fmts_count, ws_out_clpbrd_changed, task_info);
+}
+
+static void
+clipboard_send_data(uint8_t *buf, size_t buf_size, void *task_info)
+{
+ send_typed_proto_msg(buf, buf_size, ws_out_clpbrd_data, task_info);
+}
+
+static void
+clipboard_request_data(uint8_t data_fmt, void *task_info)
+{
+ send_typed_proto_msg(
+ &data_fmt, 1, ws_out_clpbrd_request_data, task_info);
+}
+
+static void
+ft_request(const wrdp_backend_ft_file_request *req, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)req,
+ sizeof(wrdp_backend_ft_file_request), ws_out_ft_request, task_info);
+}
+
+static void
+ft_send_chunk(
+ const wrdp_backend_ft_chunk *chunk, uint8_t *data, void *task_info)
+{
+ size_t buf_size = sizeof(wrdp_backend_ft_chunk) + chunk->size;
+ uint8_t *buf = malloc(buf_size);
+ if (!buf)
+ {
+ perror("malloc");
+ return;
+ }
+ memcpy(buf, chunk, sizeof(wrdp_backend_ft_chunk));
+ memcpy(buf + sizeof(wrdp_backend_ft_chunk), data, chunk->size);
+ send_typed_proto_msg(buf, buf_size, ws_out_ft_chunk, task_info);
+ free(buf);
+}
+
+static void
+ft_finish(const wrdp_backend_ft_status *status, void *task_info)
+{
+ send_typed_proto_msg((uint8_t *)status, sizeof(wrdp_backend_ft_status),
+ ws_out_ft_finish, task_info);
+}
+
+static void *
+get_libev_loop(void *_task_info)
+{
+ task_info *t = _task_info;
+ wrdp_thpool_task *pt = t->wrdp_thpool_task;
+ return pt->thread->ev_th_loop;
+}
+
+static void
+task_finished(bool success, void *_task_info)
+{
+ /* TODO: handle task failure */
+ destroy_task(_task_info);
+}
+
+static void
+reset_idle(void *_task_info)
+{
+ task_info *t = _task_info;
+ t->idle_time = 0;
+}
+
+bool
+init_exports()
+{
+ g_globals.exports = calloc(1, sizeof(wrdp_core_exports));
+ if (!g_globals.exports)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_core = calloc(1, sizeof(wrdp_core_cb_core));
+ if (!g_globals.exports->api_core)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_msgs = calloc(1, sizeof(wrdp_core_cb_msgs));
+ if (!g_globals.exports->api_msgs)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_paint = calloc(1, sizeof(wrdp_core_cb_paint));
+ if (!g_globals.exports->api_paint)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_clipboard
+ = calloc(1, sizeof(wrdp_core_cb_clipboard));
+ if (!g_globals.exports->api_clipboard)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_filetransfers
+ = calloc(1, sizeof(wrdp_core_cb_filetransfer));
+ if (!g_globals.exports->api_filetransfers)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+ g_globals.exports->api_utils = calloc(1, sizeof(wrdp_core_cb_utils));
+ if (!g_globals.exports->api_utils)
+ {
+ perror("calloc");
+ exit(EXIT_FAILURE);
+ }
+
+ g_globals.exports->api_paint->send_begin_paint = send_begin_paint;
+ g_globals.exports->api_paint->send_bitmap = send_bitmap;
+ g_globals.exports->api_paint->send_end_paint = send_end_paint;
+ g_globals.exports->api_paint->send_multi_opaque_rect
+ = send_multi_opaque_rect;
+ g_globals.exports->api_paint->send_opaque_rect_order
+ = send_opaque_rect_order;
+ g_globals.exports->api_paint->send_pat_blt = send_pat_blt;
+ g_globals.exports->api_paint->send_ptr_free = send_ptr_free;
+ g_globals.exports->api_paint->send_ptr_new = send_ptr_new;
+ g_globals.exports->api_paint->send_ptr_set = send_ptr_set;
+ g_globals.exports->api_paint->send_ptr_set_default
+ = send_ptr_set_default;
+ g_globals.exports->api_paint->send_ptr_set_null = send_ptr_set_null;
+ g_globals.exports->api_paint->send_scr_blt = send_scr_blt;
+ g_globals.exports->api_paint->send_set_bounds = send_set_bounds;
+
+ g_globals.exports->api_msgs->send_text_msg = send_text_msg;
+ g_globals.exports->api_msgs->send_text_info_msg = send_text_info_msg;
+ g_globals.exports->api_msgs->send_text_warning_msg
+ = send_text_warning_msg;
+ g_globals.exports->api_msgs->send_text_debug_msg = send_text_debug_msg;
+ g_globals.exports->api_msgs->send_ctl_msg = send_ctl_msg;
+ g_globals.exports->api_msgs->send_error_msg = send_error_msg;
+ g_globals.exports->api_msgs->send_termination_msg
+ = send_termination_msg;
+ g_globals.exports->api_core->get_libev_loop = get_libev_loop;
+ g_globals.exports->api_core->task_finished = task_finished;
+ g_globals.exports->api_core->reset_idle = reset_idle;
+
+ g_globals.exports->api_clipboard->send_data = clipboard_send_data;
+ g_globals.exports->api_clipboard->clipboard_changed = clipboard_changed;
+ g_globals.exports->api_clipboard->request_data = clipboard_request_data;
+
+ g_globals.exports->api_filetransfers->ft_finish = ft_finish;
+ g_globals.exports->api_filetransfers->ft_request = ft_request;
+ g_globals.exports->api_filetransfers->ft_send_chunk = ft_send_chunk;
+
+ g_globals.exports->api_utils->hex_print = hex_print;
+ g_globals.exports->api_utils->log_msg = log_msg;
+ g_globals.exports->api_utils->log_msg_ex = log_msg_ex;
+ return true;
+}
diff --git a/src/core/exports.h b/src/core/exports.h
new file mode 100644
index 0000000..fe23c4c
--- /dev/null
+++ b/src/core/exports.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void send_error_msg(const char *msg, void *task_info);
+void send_text_msg(const char *msg, void *task_info);
+void send_text_info_msg(const char *msg, void *task_info);
+void send_text_warning_msg(const char *msg, void *task_info);
+void send_text_debug_msg(const char *msg, void *task_info);
+void send_ctl_msg(const char *msg, void *task_info);
+void send_termination_msg(void *task_info);
+
+bool init_exports();
diff --git a/src/core/globals.h b/src/core/globals.h
new file mode 100644
index 0000000..5d12d64
--- /dev/null
+++ b/src/core/globals.h
@@ -0,0 +1,82 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <limits.h>
+#include <stdbool.h>
+
+#include <sys/queue.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+#include "wrdp_thpool.h"
+
+#define PROG_NAME "webrdp"
+
+#include "ws_session.h"
+#include "task.h"
+
+typedef struct
+{
+ /* set worker thread count and max tasks per thread for thread pool */
+ int32_t thread_count, tasks_per_thread;
+
+ /* set core log level to one of "wrdp_log_level_e" */
+ uint8_t log_level;
+
+ /* set true to run in background (daemon mode) */
+ bool daemon;
+
+ /* global session settings defaults */
+ ws_session_settings ws_session_defaults;
+
+ /* websocket server settings:
+ */
+
+ /* port for http(s)/ws server */
+ int32_t ws_port, ctl_port;
+
+ /* unix socket path */
+ char *ws_socket_path, *ctl_socket_path;
+
+ /* url of external auth server */
+ char *auth_server_url;
+
+ /* 512bit key used for hmac
+ * used additional 2 bytes in buffer
+ * required for base64decode
+ */
+ char secret_key_verify[66], secret_key_sign[66];
+
+ /* paths to cafile and/or cafile dir */
+ char *ctl_ssl_cafile, *ctl_ssl_capath, *ctl_ssl_cert, *ctl_ssl_key;
+
+} global_settings;
+
+struct task_s
+{
+ task_info *task;
+ LIST_ENTRY(task_s) entries;
+};
+
+struct backend_s
+{
+ wrdp_backend_module *backend;
+ LIST_ENTRY(backend_s) entries;
+};
+
+typedef struct
+{
+ global_settings settings;
+ wrdp_thpool *thpool;
+ wrdp_core_exports *exports;
+ LIST_HEAD(backend_head_, backend_s) backends_head;
+} globals;
+
+extern globals g_globals;
+
+void shutdown_core();
diff --git a/src/core/include/.clang-format b/src/core/include/.clang-format
new file mode 120000
index 0000000..3260daf
--- /dev/null
+++ b/src/core/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format \ No newline at end of file
diff --git a/src/core/include/webrdp_api_shared_structures.h b/src/core/include/webrdp_api_shared_structures.h
new file mode 100644
index 0000000..c5ffbef
--- /dev/null
+++ b/src/core/include/webrdp_api_shared_structures.h
@@ -0,0 +1,222 @@
+#pragma once
+
+/* generic structures */
+
+typedef struct
+{
+ char *name, *value;
+} backend_setting_str;
+
+typedef struct
+{
+ char *name;
+ int64_t value;
+} backend_setting_int;
+
+typedef enum
+{
+ ws_keycomb_ctrlaltdel_press,
+ ws_keycomb_alttab_press,
+ ws_keycomb_alttab_release,
+ ws_keycomb_unused
+} ws_input_keycomb;
+
+/* user input structures */
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t x, y, flags;
+} ws_input_mouse;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t code;
+ bool down;
+} ws_input_kupdown;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t code, shiftstate;
+} ws_input_kpress;
+
+typedef struct __attribute__((__packed__))
+{
+ const uint32_t *input;
+ size_t length;
+} ws_input_unicode;
+
+/* display output structures */
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t x; /* Destination X */
+ uint32_t y; /* Destination Y */
+ uint32_t width; /* Width */
+ uint32_t height; /* Height */
+ uint32_t dest_width; /* Destination Width */
+ uint32_t dest_height; /* Destination Height */
+ uint32_t bpp; /* Bits per Pixel */
+ uint32_t compressed; /* Flag: Compressed */
+ uint32_t size; /* DataSize */
+} wrdp_core_display_bmp;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t left;
+ int32_t top;
+ int32_t right;
+ int32_t bottom;
+} wrdp_core_display_bounds;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t nLeftRect;
+ int32_t nTopRect;
+ int32_t nWidth;
+ int32_t nHeight;
+ uint32_t color;
+} wrdp_core_display_opaque_rect_order;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ uint32_t fg;
+ uint32_t rop;
+} wrdp_core_display_patblt_order;
+
+typedef struct __attribute__((__packed__))
+{
+ int32_t left;
+ int32_t top;
+ int32_t width;
+ int32_t height;
+} wrdp_core_display_delta_rect;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t color;
+ uint32_t num_rect;
+ wrdp_core_display_delta_rect *rects;
+} wrdp_core_display_m_opaque_rect;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t rop;
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ int32_t sx;
+ int32_t sy;
+} wrdp_core_display_scr_blt;
+
+typedef struct __attribute__((__packed__))
+{
+ uint8_t resL; /* 2 bytes reserved must always be 0 */
+ uint8_t resH;
+ uint8_t imgTypeL; /* 2 bytes image type. 1 = ICO, 2 = CUR */
+ uint8_t imgTypeH;
+ uint8_t nbImgL; /* 2 bytes number of images */
+ uint8_t nbImgH;
+ uint8_t width; /* 1 byte image width in pixels */
+ uint8_t height; /* 1 byte image height in pixels */
+ uint8_t nbColors; /* 1 bytenumber of colors. 0 if not using a color
+ pallete */
+ uint8_t res; /* 1 byte reserved, should be 0 */
+ uint8_t
+ xPosL; /* 2 bytes of hot spot x (for ICO, color planes, 0 or 1) */
+ uint8_t xPosH;
+ uint8_t yPosL; /* 2 bytes of hot spot y (for ICO bits per pixel) */
+ uint8_t yPosH;
+ uint8_t sizeLL; /* 4 bytes of image size */
+ uint8_t sizeLH;
+ uint8_t sizeHL;
+ uint8_t sizeHH;
+ uint8_t offsetLL; /* 4 bytes of offset of the data */
+ uint8_t offsetLH;
+ uint8_t offsetHL;
+ uint8_t offsetHH;
+ uint8_t *data;
+} wrdp_core_display_cursor;
+
+typedef struct
+{
+ uint32_t id;
+ uint32_t hx;
+ uint32_t hy;
+ uint32_t data_size;
+ wrdp_core_display_cursor *cur;
+} wrdp_core_display_cursor_info;
+
+/* clipboard structures */
+
+typedef enum
+{
+ clip_format_unsupported = -1,
+ clip_format_raw,
+ clip_format_text,
+ clip_format_unicode,
+ clip_format_file_list
+} wrdp_enum_clip_format;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ wrdp_enum_clip_format format;
+} wrdp_backend_clipbrd_data_request;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ uint8_t count, *formats;
+} wrdp_backend_clipbrd_fmts;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ /* TODO: */
+ uint32_t size;
+ uint8_t *data;
+} wrdp_backend_clipbrd_data;
+
+/* filetransfer structures */
+
+typedef struct __attribute__((__packed__))
+{
+ uint16_t filename_len;
+ uint32_t file_id;
+ uint64_t file_size;
+} wrdp_backend_ft_list_entry;
+
+typedef struct __attribute__((__packed__))
+{
+ uint32_t file_id;
+ uint64_t req_size;
+ uint64_t file_offset;
+} wrdp_backend_ft_file_request;
+
+typedef enum
+{
+ ft_status_success = 0,
+ ft_status_failure,
+ ft_status_already_runing
+} wrdp_backend_enum_ft_init_status;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ /* TODO: */
+ uint32_t file_id, transfer_id;
+ uint8_t status;
+} wrdp_backend_ft_status;
+
+typedef struct __attribute__((__packed__))
+{
+ /* DRAFT! */
+ /* TODO: */
+ uint32_t transfer_id;
+ uint32_t size;
+} wrdp_backend_ft_chunk;
diff --git a/src/core/include/webrdp_api_utils.h b/src/core/include/webrdp_api_utils.h
new file mode 100644
index 0000000..170ca2b
--- /dev/null
+++ b/src/core/include/webrdp_api_utils.h
@@ -0,0 +1,43 @@
+#pragma once
+
+typedef enum
+{
+ wrdp_log_level_error,
+ wrdp_log_level_warning,
+ wrdp_log_level_info,
+ wrdp_log_level_debug,
+ wrdp_log_level_trace
+} wrdp_log_level_e;
+
+static const uint16_t wrdp_log_flag_binary = 0x0001;
+
+typedef struct _log_msg_info
+{
+ /* message buffer:
+ * containing message which will be displayed
+ *
+ * mandatory parameter */
+ const uint8_t *buf;
+
+ /* message size:
+ * size of message in buiffer which must be displayed
+ *
+ * optional in case of null terminated string, mandatory otherwise */
+ size_t buf_size;
+
+ /* log level for this message
+ *
+ * optional, default is 'wrdp_log_level_error' */
+ wrdp_log_level_e level;
+
+ /* message flags:
+ * currently supported flags is:
+ * 'wrdp_log_flag_binary': print message as hex data
+ * optional, default is plain text */
+ uint16_t flags;
+
+ /* pointers to various data structures which may be usefule
+ * during debugging
+ * optional */
+ void *task_info, *wrdp_thpool_task, *ws_session;
+} log_msg_info;
diff --git a/src/core/include/webrdp_core_api.h b/src/core/include/webrdp_core_api.h
new file mode 100644
index 0000000..36ef948
--- /dev/null
+++ b/src/core/include/webrdp_core_api.h
@@ -0,0 +1,185 @@
+#pragma once
+/* API functions exported by core which can be used in backend modules will
+ * be defined here.
+ */
+
+#include "webrdp_api_shared_structures.h"
+#include "webrdp_api_utils.h"
+
+typedef struct
+{
+ /* Send text message */
+ void (*send_text_msg)(const char *msg, void *task_info);
+
+ /* Send text info message */
+ void (*send_text_info_msg)(const char *msg, void *task_info);
+
+ /* Send text warning message */
+ void (*send_text_warning_msg)(const char *msg, void *task_info);
+
+ /* Send text debug message */
+ void (*send_text_debug_msg)(const char *msg, void *task_info);
+
+ /* Send error message, this also causing disconnect
+ * TODO: do not send termination message on errors, use this instead
+ * OR disable automatic disconnect on errors
+ */
+ void (*send_error_msg)(const char *msg, void *task_info);
+
+ /* Send control message to js client
+ * TODO: do we need to provide it for backend modules ?
+ */
+ void (*send_ctl_msg)(const char *msg, void *task_info);
+
+ /* Send session termination message on quit */
+ void (*send_termination_msg)(void *task_info);
+} wrdp_core_cb_msgs;
+
+typedef struct
+{
+ void (*send_begin_paint)(void *task_info);
+ void (*send_end_paint)(void *task_info);
+
+ /* Single bitmap */
+ void (*send_bitmap)(const wrdp_core_display_bmp *bmp, uint8_t *bmp_data,
+ void *task_info);
+
+ /* Primary: OPAQUE_RECT_ORDER */
+ void (*send_opaque_rect_order)(
+ const wrdp_core_display_opaque_rect_order *oro, void *task_info);
+
+ /* SetBounds */
+ void (*send_set_bounds)(
+ const wrdp_core_display_bounds *bounds, void *task_info);
+
+ /* PatBlt */
+ void (*send_pat_blt)(
+ const wrdp_core_display_patblt_order *patblt, void *task_info);
+
+ /* Multi Opaque rect */
+ void (*send_multi_opaque_rect)(
+ const wrdp_core_display_m_opaque_rect *mrect, void *task_info);
+
+ /* ScrBlt */
+ void (*send_scr_blt)(
+ const wrdp_core_display_scr_blt *scr_blt, void *task_info);
+
+ /* PTR_NEW
+ * uint32_t id, xhot, yhot
+ */
+ void (*send_ptr_new)(
+ const wrdp_core_display_cursor_info *ptr, void *task_info);
+
+ /* PTR_FREE
+ * uint32_t id
+ */
+ void (*send_ptr_free)(uint32_t ptr_id, void *task_info);
+
+ /* PTR_SET
+ * uint32_t id
+ */
+ void (*send_ptr_set)(uint32_t ptr_id, void *task_info);
+
+ /* PTR_SETNULL
+ */
+ void (*send_ptr_set_null)(void *task_info);
+
+ /* PTR_SETDEFAULT
+ */
+ void (*send_ptr_set_default)(void *task_info);
+
+} wrdp_core_cb_paint;
+
+typedef struct
+{
+ /* DRAFT! */
+ /* TODO: */
+
+ /*
+ * uint8_t* wrdp_enum_clip_format array
+ */
+ void (*clipboard_changed)(
+ uint8_t *fmts, size_t fmts_count, void *task_info);
+
+ /*
+ * clipboard data array
+ */
+ void (*send_data)(uint8_t *buf, size_t buf_size, void *task_info);
+
+ /*
+ * request clipboard data from client
+ */
+ void (*request_data)(uint8_t data_fmt, void *task_info);
+
+} wrdp_core_cb_clipboard;
+
+typedef struct
+{
+ /* DRAFT! */
+
+ /*
+ * TODO:
+ */
+
+ void (*ft_request)(
+ const wrdp_backend_ft_file_request *req, void *task_info);
+
+ /*
+ * TODO:
+ */
+ void (*ft_send_chunk)(
+ const wrdp_backend_ft_chunk *chunk, uint8_t *data, void *task_info);
+
+ /*
+ * TODO:
+ */
+ void (*ft_finish)(
+ const wrdp_backend_ft_status *status, void *task_info);
+
+} wrdp_core_cb_filetransfer;
+
+typedef struct
+{
+ /* access to variables allocated by core: */
+
+ /* get libev ev_loop* for calling thread */
+ void *(*get_libev_loop)(void *task_info);
+
+ /* task runtime state functions: */
+
+ /* inform core about task is finished */
+ void (*task_finished)(bool success, void *task_info);
+
+ /* reset task idle time to 0 */
+ void (*reset_idle)();
+} wrdp_core_cb_core;
+
+typedef struct
+{
+ /* print raw data buffer to stdout */
+ void (*hex_print)(const uint8_t *buf, size_t buf_len);
+
+ /* print log message, currently only stdout is supported */
+ void (*log_msg)(const uint8_t *buf, size_t buf_len,
+ wrdp_log_level_e type, uint16_t flags);
+ /* print log message extended, currently only stdout is supported */
+ void (*log_msg_ex)(log_msg_info *i);
+} wrdp_core_cb_utils;
+
+typedef struct
+{
+ /* client<>server protocol functions: */
+ wrdp_core_cb_msgs *api_msgs;
+
+ /* Painting functions: */
+ wrdp_core_cb_paint *api_paint;
+
+ wrdp_core_cb_core *api_core;
+
+ wrdp_core_cb_clipboard *api_clipboard;
+
+ wrdp_core_cb_filetransfer *api_filetransfers;
+
+ wrdp_core_cb_utils *api_utils;
+
+} wrdp_core_exports;
diff --git a/src/core/include/webrdp_module_api.h b/src/core/include/webrdp_module_api.h
new file mode 100644
index 0000000..7f2d635
--- /dev/null
+++ b/src/core/include/webrdp_module_api.h
@@ -0,0 +1,109 @@
+#pragma once
+
+/* Short api usage brief...
+ * each backend module must export:
+ * 1. bool <backend_name>_create(wrdp_core_exports*, wrdp_backend_module*)
+ * which initialize passed wrdp_backend_module* instance
+ * with all required callbacks.
+ * wrdp_core_exports* ptr should be stored by backend,
+ * for example in "internals" structure.
+ * return "true" on success
+ */
+
+#include <stdint.h>
+
+#include "webrdp_api_shared_structures.h"
+
+//#include "webrdp_core_api.h"
+
+/* API used by backend modules defined here */
+
+typedef struct
+{
+ /* Init function exported by backend, this function should fully
+ * initialize backend.
+ * retrun false on failure
+ * required. */
+ bool (*init)(void *backend_internals);
+
+ /* Destroy backend function should deinitialize backend
+ * and free used memory
+ * required. */
+ void (*destroy)(void *backend_internals);
+
+ /* Pass task_info internal structure ptr to the backend.
+ * must be stored and suplied to core on need
+ * required. */
+ void (*set_task_info)(void *task_info, void *internals);
+
+ /* Backend settings related functions
+ * return false on failure.
+ * required. */
+ bool (*set_setting_str)(backend_setting_str *setting, void *internals);
+ bool (*set_setting_int)(backend_setting_int *setting, void *internals);
+} wrdp_backend_cb_module;
+
+typedef struct
+{
+ /* Backend ws-protocol handlers callbacks
+ * return false on failure
+ * at least one of the following is required*/
+ bool (*mouse)(ws_input_mouse input, void *internals);
+ bool (*kupdown)(ws_input_kupdown input, void *internals);
+ bool (*kpress)(ws_input_kpress input, void *internals);
+ bool (*kcomb)(ws_input_keycomb input, void *internals);
+ bool (*unicode)(ws_input_unicode input, void *internals);
+} wrdp_backend_cb_input;
+
+typedef struct
+{
+ /* DRAFT! */
+ /* TODO: */
+
+ bool (*request_data)(
+ const wrdp_backend_clipbrd_data_request *, void *backend_internals);
+ bool (*data_changed)(
+ const wrdp_backend_clipbrd_fmts *, void *backend_internals);
+ bool (*send_data)(
+ const wrdp_backend_clipbrd_data *, void *backend_internals);
+
+} wrdp_backend_cb_clipboard;
+
+typedef struct
+{
+ /* DRAFT! */
+ /* TODO: */
+
+ bool (*request)(
+ const wrdp_backend_ft_file_request *, void *backend_internals);
+ bool (*chunk)(const wrdp_backend_ft_chunk *, const uint8_t *data,
+ void *backend_internals);
+ bool (*finish)(const wrdp_backend_ft_status *, void *backend_internals);
+} wrdp_backend_cb_filetransfer;
+
+typedef struct
+{
+
+ /* Backend internal data, does not touched by core. */
+ void *backend_internals;
+
+ /* pointer to wrdp_thpool_task* used by some internals */
+ void *wrdp_thpool_task;
+
+ /* Variable indicate what task destruction is in progress,
+ * task should not use any of core exports anymore */
+ bool stopped;
+
+ /* Information about task used by core,
+ * must be passed to functions which require it */
+ void *task_info;
+
+ /* ws_session list */
+ void *sessions_list_head;
+
+ wrdp_backend_cb_module *callbacks_module;
+ wrdp_backend_cb_input *callbacks_input;
+ wrdp_backend_cb_clipboard *callbacks_clipbrd;
+ wrdp_backend_cb_filetransfer *callbacks_ft;
+
+} wrdp_backend_module;
diff --git a/src/core/json_helpers.c b/src/core/json_helpers.c
new file mode 100644
index 0000000..dfbef34
--- /dev/null
+++ b/src/core/json_helpers.c
@@ -0,0 +1,275 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <json.h>
+
+int64_t
+json_option_extract_int64(struct json_object_element_s *json_option)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ {
+ return 0;
+ }
+ break;
+ case json_type_false:
+ {
+ return 0;
+ }
+ break;
+ case json_type_true:
+ {
+ return 1;
+ }
+ break;
+ case json_type_number:
+ {
+ struct json_number_s *jsopt
+ = (struct json_number_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->number_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ int64_t ret = false;
+ strncpy(out, jsopt->number, jsopt->number_size);
+ out[jsopt->number_size] = 0;
+ ret = atoll(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->string_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ int64_t ret = false;
+ strncpy(out, jsopt->string, jsopt->string_size);
+ out[jsopt->string_size] = 0;
+ ret = atoll(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_object:
+ {
+ return 1;
+ }
+ break;
+ default:
+ {
+ return 0;
+ }
+ break;
+ }
+ return 0;
+}
+
+bool
+json_option_extract_bool(struct json_object_element_s *json_option)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ {
+ return false;
+ }
+ break;
+ case json_type_false:
+ {
+ return false;
+ }
+ break;
+ case json_type_true:
+ {
+ return true;
+ }
+ break;
+ case json_type_number:
+ {
+ struct json_number_s *jsopt
+ = (struct json_number_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->number_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ bool ret = false;
+ strncpy(out, jsopt->number, jsopt->number_size);
+ out[jsopt->number_size] = 0;
+ ret = atoi(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ char *out = malloc(jsopt->string_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ bool ret = false;
+ strncpy(out, jsopt->string, jsopt->string_size);
+ out[jsopt->string_size] = 0;
+ ret = atoi(out);
+ free(out);
+ return ret;
+ }
+ break;
+ case json_type_object:
+ {
+ return true;
+ }
+ break;
+ default:
+ {
+ return false;
+ }
+ break;
+ }
+ return false;
+}
+
+char *
+json_option_extract_string(
+ struct json_object_element_s *json_option, char *_out)
+{
+ char *out = 0;
+ if (_out)
+ {
+ out = _out;
+ }
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("null") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "null");
+ return out;
+ }
+ break;
+ case json_type_false:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("false") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "false");
+ return out;
+ }
+ break;
+ case json_type_true:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("true") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "true");
+ return out;
+ }
+ break;
+ case json_type_number:
+ {
+ struct json_number_s *jsopt
+ = (struct json_number_s *)
+ json_option->value->payload;
+ if (!out)
+ {
+ out = malloc(jsopt->number_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strncpy(out, jsopt->number, jsopt->number_size);
+ out[jsopt->number_size] = 0;
+ return out;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ if (!out)
+ {
+ out = malloc(jsopt->string_size + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strncpy(out, jsopt->string, jsopt->string_size);
+ out[jsopt->string_size] = 0;
+ return out;
+ }
+ break;
+ case json_type_object:
+ {
+ if (!out)
+ {
+ out = malloc(strlen("object") + 1);
+ if (!out)
+ {
+ perror("malloc");
+ return 0;
+ }
+ }
+ strcpy(out, "object");
+ return out;
+ }
+ break;
+ default:
+ {
+ return out;
+ }
+ break;
+ }
+ return out;
+}
diff --git a/src/core/json_helpers.h b/src/core/json_helpers.h
new file mode 100644
index 0000000..953e56f
--- /dev/null
+++ b/src/core/json_helpers.h
@@ -0,0 +1,15 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+int64_t json_option_extract_int64(struct json_object_element_s *json_option);
+
+bool json_option_extract_bool(struct json_object_element_s *json_option);
+
+/* if out buffer is 0, caller must free memory */
+char *json_option_extract_string(
+ struct json_object_element_s *json_option, char *out);
diff --git a/src/core/log.c b/src/core/log.c
new file mode 100644
index 0000000..5671d90
--- /dev/null
+++ b/src/core/log.c
@@ -0,0 +1,164 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include "webrdp_core_api.h"
+
+#include "globals.h"
+
+#include "log.h"
+#include "utilities.h"
+
+#include "ws_session.h"
+#include "wrdp_thpool.h"
+
+/*
+ * sample:
+ * [D 190318 20:25:24 main.c:666]
+ */
+
+static void
+print_time()
+{
+ time_t t = time(0);
+ struct tm *ptm = localtime(&t);
+
+ if (!ptm)
+ printf("%ld", t);
+ else
+ printf("%02d-%02d %02d:%02d:%02d", ptm->tm_mon, ptm->tm_mday,
+ ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+}
+
+static void
+binary_print(const uint8_t *buf, size_t buf_len)
+{
+ hex_print(buf, buf_len);
+}
+
+static void
+text_print(const uint8_t *buf, size_t buf_len)
+{
+ printf("%.*s", (int)buf_len, buf);
+}
+
+static void
+print_msg_level(wrdp_log_level_e *level)
+{
+ char l;
+ switch (*level)
+ {
+ case wrdp_log_level_debug:
+ {
+ l = 'D';
+ }
+ break;
+ case wrdp_log_level_warning:
+ {
+ l = 'W';
+ }
+ break;
+ case wrdp_log_level_error:
+ {
+ l = 'E';
+ }
+ break;
+ case wrdp_log_level_trace:
+ {
+ l = 'T';
+ }
+ break;
+ case wrdp_log_level_info:
+ default:
+ {
+ l = 'I';
+ }
+ break;
+ }
+ printf("%c", l);
+}
+
+static void
+print_sender_info(log_msg_info *msg_info)
+{
+ task_info *info = msg_info->task_info;
+ wrdp_thpool_task *t = msg_info->wrdp_thpool_task;
+ ws_session *s = msg_info->ws_session;
+ if (!t)
+ {
+ if (info && info->wrdp_thpool_task)
+ t = info->wrdp_thpool_task;
+ else if (s && s->wrdp_thpool_task)
+ t = s->wrdp_thpool_task;
+ else if (s && s->task_info)
+ {
+ task_info *i = s->task_info;
+ if (i->wrdp_thpool_task)
+ t = i->wrdp_thpool_task;
+ }
+ }
+ if (!info)
+ {
+ if (s && s->task_info)
+ info = s->task_info;
+ }
+ if (t)
+ printf(" task: %p", t);
+ if (info)
+ printf(" task_info: %p", info);
+ if (s)
+ printf(" ws_session: %p", s);
+}
+
+static void
+format_prefix(log_msg_info *msg_info)
+{
+ printf("[");
+ print_msg_level(&(msg_info->level));
+ printf(" ");
+ print_time();
+ print_sender_info(msg_info);
+ printf("] ");
+}
+
+void
+log_msg(
+ const uint8_t *buf, size_t buf_len, wrdp_log_level_e level, uint16_t flags)
+{
+ log_msg_info i = {0};
+ i.buf = buf;
+ i.buf_size = buf_len;
+ i.flags = flags;
+ i.level = level;
+ log_msg_ex(&i);
+}
+
+void
+log_msg_ex(log_msg_info *msg_info)
+{
+ size_t buf_size;
+ if (msg_info->level > g_globals.settings.log_level)
+ {
+ return;
+ }
+ buf_size = msg_info->buf_size;
+ if (!buf_size)
+ buf_size = strlen((const char *)msg_info->buf);
+ format_prefix(msg_info);
+ if (msg_info->flags & wrdp_log_flag_binary)
+ {
+ binary_print(msg_info->buf, buf_size);
+ }
+ else
+ {
+ text_print(msg_info->buf, buf_size);
+ }
+ printf("\n");
+}
diff --git a/src/core/log.h b/src/core/log.h
new file mode 100644
index 0000000..e8dde39
--- /dev/null
+++ b/src/core/log.h
@@ -0,0 +1,12 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void log_msg(
+ const uint8_t *buf, size_t buf_len, wrdp_log_level_e type, uint16_t flags);
+
+void log_msg_ex(log_msg_info *msg_info);
diff --git a/src/core/main.c b/src/core/main.c
new file mode 100644
index 0000000..6572938
--- /dev/null
+++ b/src/core/main.c
@@ -0,0 +1,220 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <ev.h>
+
+#include <stdio.h>
+#include <execinfo.h>
+#include <libunwind.h>
+
+#include "cmdline.h"
+#include "config_file.h"
+#include "ev_loop.h"
+#include "exports.h"
+#include "globals.h"
+#include "thread_impl.h"
+#include "utilities.h"
+#include "wrdp_thpool.h"
+#include "ws_session.h"
+#include "remote_control.h"
+#include "curl_helpers.h"
+#include "thread_sync.h"
+
+globals g_globals;
+
+static void
+custom_thread_init(void *user_pool_data, wrdp_thpool_thread *t)
+{
+ per_thread_curl_init(user_pool_data, t);
+}
+
+static void
+init_internals()
+{
+ if (g_globals.settings.thread_count <= 0)
+ {
+ g_globals.settings.thread_count = sysconf(_SC_NPROCESSORS_ONLN);
+ }
+ if (g_globals.settings.thread_count <= 0)
+ {
+ g_globals.settings.thread_count = 2;
+ }
+ if (!init_exports())
+ {
+ printf("error: failed to initialize core exports\n");
+ exit(EXIT_FAILURE);
+ }
+ /* random seed */
+ srand(time(0) + 13);
+
+ LIST_INIT(&(g_globals.backends_head));
+
+ void *c = init_curl_global();
+ g_globals.thpool = wrdp_thpool_create(g_globals.settings.thread_count,
+ g_globals.settings.tasks_per_thread, custom_thread_init, 0, 0, 0,
+ pool_message_handler, thread_message_handler, EV_DEFAULT, c);
+}
+
+static void
+settings_load_default()
+{
+ memset(&g_globals, 0, sizeof(globals));
+ memset(&g_globals.settings, 0, sizeof(global_settings));
+ g_globals.settings.secret_key_verify[0] = 0;
+ g_globals.settings.secret_key_sign[0] = 0;
+ g_globals.settings.thread_count = -1;
+}
+
+static void
+settings_validate()
+{
+ bool failure = false;
+ if (!g_globals.settings.ws_port)
+ g_globals.settings.ws_port = 8080;
+ if (!g_globals.settings.ctl_port)
+ g_globals.settings.ctl_port = 13666;
+ if (!g_globals.settings.tasks_per_thread)
+ g_globals.settings.tasks_per_thread = 1024;
+ if (!g_globals.settings.secret_key_verify[0])
+ {
+ printf("\"secret_key_verify\" must be set\n");
+ failure = true;
+ }
+ if (!g_globals.settings.secret_key_sign[0])
+ {
+ strcpy(g_globals.settings.secret_key_sign,
+ g_globals.settings.secret_key_verify);
+ }
+ if (!g_globals.settings.ctl_ssl_cert)
+ {
+ printf("\"ctl_ssl_cert\" must be set\n");
+ failure = true;
+ }
+ if (!g_globals.settings.ctl_ssl_key)
+ {
+ printf("\"ctl_ssl_key\" must be set\n");
+ failure = true;
+ }
+ if (failure)
+ {
+ exit(EXIT_FAILURE);
+ }
+}
+
+void
+shutdown_core()
+{
+ free(g_globals.exports);
+}
+
+static void
+daemonize()
+{
+ pid_t p = fork();
+ if (p)
+ {
+ printf("child process forked, exiting parent\n");
+ exit(EXIT_SUCCESS);
+ }
+ /* TODO: */
+}
+
+/* https://stackoverflow.com/questions/77005/how-to-automatically
+ * -generate-a-stacktrace-when-my-program-crashes */
+/*static void
+sigsegv_handler(int sig) {
+ void *array[100];
+ size_t size;
+
+ // get void*'s for all entries on the stack
+ size = backtrace (array, 100);
+
+ // print out all the frames to stderr
+ fprintf(stderr, "Error: signal %d:\n", sig);
+ backtrace_symbols_fd (array, size, STDERR_FILENO);
+ exit (EXIT_FAILURE);
+} */
+
+#ifdef DEBUG
+/* https://github.com/cslarsen/libunwind-examples/blob/master/backtrace.cpp */
+static void
+sigsegv_handler(int sig)
+{
+ unw_cursor_t cursor;
+ unw_context_t context;
+
+ unw_getcontext(&context);
+ unw_init_local(&cursor, &context);
+
+ int n = 0;
+ while (unw_step(&cursor) > 0)
+ {
+ unw_word_t ip, sp, off;
+
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ unw_get_reg(&cursor, UNW_REG_SP, &sp);
+
+ char symbol[256] = {"<unknown>"};
+ char *name = symbol;
+
+ unw_get_proc_name(&cursor, symbol, sizeof(symbol), &off);
+
+ printf("#%-2d 0x%016" PRIxPTR " sp=0x%016" PRIxPTR
+ " %s + 0x%" PRIxPTR "\n",
+ ++n, ip, sp, name, off);
+
+ if (name != symbol)
+ free(name);
+ }
+ exit(EXIT_FAILURE);
+}
+
+/* this is required to generate gprof profile */
+static void
+sigusr2_handler(int sif)
+{
+ exit(EXIT_SUCCESS);
+}
+
+#endif
+
+int
+main(int argc, char **argv)
+{
+#ifdef DEBUG
+ signal(SIGSEGV, sigsegv_handler);
+ signal(SIGUSR2, sigusr2_handler);
+#endif
+ settings_load_default();
+ handle_cmdline_args(argc, argv);
+ if (g_globals.settings.daemon)
+ {
+ daemonize();
+ }
+ settings_validate();
+
+ init_internals();
+
+ {
+ int ws_tcp_sock = ws_server_init();
+ int ws_unix_sock = ws_server_init_unix();
+ int ctl_server_tcp_sock = ctl_server_init_tcp();
+ int ctl_server_unix_sock = ctl_server_init_unix();
+
+ //init_remote_control();
+
+ run_ev_loop_main(ws_tcp_sock, ws_unix_sock, ctl_server_tcp_sock,
+ ctl_server_unix_sock);
+ }
+
+ shutdown_core();
+ return 0;
+}
diff --git a/src/core/remote_control.c b/src/core/remote_control.c
new file mode 100644
index 0000000..90449d8
--- /dev/null
+++ b/src/core/remote_control.c
@@ -0,0 +1,435 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <openssl/err.h>
+#include <ev.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <json.h>
+
+#include "globals.h"
+#include "socket_helpers.h"
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "remote_control.h"
+#include "json_helpers.h"
+
+#include "webrdp_core_api.h"
+#include "log.h"
+
+static int ctl_server_tcp_sock = -1, ctl_server_unix_sock = -1;
+static SSL_CTX *ctx;
+
+/* TODO: find out why this code affecting global certificate store */
+
+static void
+init_openssl()
+{
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+}
+
+static SSL_CTX *
+create_context()
+{
+ const SSL_METHOD *method = TLS_server_method();
+
+ ctx = SSL_CTX_new(method);
+ if (!ctx)
+ {
+ perror("Unable to create SSL context");
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ return ctx;
+}
+
+/* this is default behaviour, no need to override */
+/*static int
+verify_callback(int verify_ok, X509_STORE_CTX *s)
+{
+ return verify_ok;
+}*/
+
+static void
+configure_context(SSL_CTX *ctx)
+{
+ if (g_globals.settings.ctl_ssl_cafile
+ || g_globals.settings.ctl_ssl_capath)
+ {
+ if (!SSL_CTX_load_verify_locations(ctx,
+ g_globals.settings.ctl_ssl_cafile,
+ g_globals.settings.ctl_ssl_capath))
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ if (!SSL_CTX_set_default_verify_paths(ctx))
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ SSL_CTX_set_ecdh_auto(ctx, 1);
+
+ /*TODO: configurable file path*/
+ /* Set the key and cert */
+ if (SSL_CTX_use_certificate_file(
+ ctx, g_globals.settings.ctl_ssl_cert, SSL_FILETYPE_PEM)
+ <= 0)
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(
+ ctx, g_globals.settings.ctl_ssl_key, SSL_FILETYPE_PEM)
+ <= 0)
+ {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ SSL_CTX_set_verify(
+ ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
+}
+
+static void
+on_ctl_task_destroy(wrdp_thpool_task *task)
+{
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ {
+ const char *msg = "cleaning task slot";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ }
+ ev_io_stop(task->thread->ev_th_loop, &(info->session->ev_con_fd_r));
+ if (ev_is_active(&(info->session->ev_con_fd_w)))
+ {
+ ev_io_stop(
+ task->thread->ev_th_loop, &(info->session->ev_con_fd_w));
+ }
+ if (session->ssl)
+ {
+ SSL_free(session->ssl);
+ }
+ close(info->session->connection_fd);
+ free(info->session);
+ free(info);
+}
+
+void
+ctl_destroy_task(wrdp_thpool_task *task)
+{
+ wrdp_thread_pool_destroy_task(task, on_ctl_task_destroy);
+}
+
+int
+ctl_server_init_tcp()
+{
+ if (g_globals.settings.ctl_port <= 0)
+ {
+ ctl_server_tcp_sock = -1;
+ return ctl_server_tcp_sock;
+ }
+ ctl_server_tcp_sock
+ = create_listen_socket_tcp(g_globals.settings.ctl_port);
+ if (ctl_server_tcp_sock != -1)
+ {
+ socket_make_non_block(ctl_server_tcp_sock);
+ }
+ return ctl_server_tcp_sock;
+}
+
+int
+ctl_server_init_unix()
+{
+ if (!g_globals.settings.ctl_socket_path
+ || !g_globals.settings.ctl_socket_path[0])
+ {
+ ctl_server_unix_sock = -1;
+ return ctl_server_unix_sock;
+ }
+ ctl_server_unix_sock
+ = create_listen_socket_unix(g_globals.settings.ctl_socket_path);
+ if (ctl_server_unix_sock != -1)
+ {
+ socket_make_non_block(ctl_server_unix_sock);
+ }
+ return ctl_server_unix_sock;
+}
+
+ctl_task_info *
+ctl_create_task(int connection_fd)
+{
+ /* TODO: handle errors */
+ ctl_task_info *info = 0;
+ ctl_session *session = 0;
+ int ssl_accept_ret = 0;
+ info = calloc(1, sizeof(ctl_task_info));
+ if (!info)
+ {
+ perror("calloc");
+ goto error;
+ }
+ session = calloc(1, sizeof(ctl_session));
+ if (!session)
+ {
+ perror("calloc");
+ goto error;
+ }
+ session->connection_fd = connection_fd;
+ info->session = session;
+ session->ssl = SSL_new(ctx);
+ SSL_set_fd(session->ssl, connection_fd);
+ ssl_accept_ret = SSL_accept(session->ssl);
+ if (ssl_accept_ret <= 0)
+ {
+ switch (SSL_get_error(session->ssl, ssl_accept_ret))
+ {
+ case SSL_ERROR_WANT_READ:
+ {
+ /* do nothing here, will be handled later */
+ }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ {
+ /* TODO:
+ * 1. set write watcher
+ * 2. do SSL_write ?
+ */
+ }
+ break;
+ default:
+ goto error;
+ }
+ }
+ else
+ {
+ session->state = ctl_session_connected;
+ }
+ return info;
+error:
+ if (session)
+ {
+ if (session->ssl)
+ {
+ SSL_free(session->ssl);
+ }
+ free(session);
+ }
+ if (info)
+ {
+ free(info);
+ }
+ return 0;
+}
+
+typedef enum
+{
+ cmd_get_sessions,
+ cmd_kill
+} cmd_type;
+
+typedef struct
+{
+ cmd_type type;
+ char **sessions, *message;
+} ctl_cmd;
+
+static bool
+ctl_handle_json_option(struct json_object_element_s *json_option,
+ wrdp_thpool_task *task, ctl_cmd *cmd)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_object:
+ {
+ }
+ break;
+ default:
+ {
+ const char *msg = "unsupported json setting type";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ break;
+ }
+ return true;
+}
+
+static bool
+ctl_handle_json_array_cmd(wrdp_thpool_task *task)
+{
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ struct json_parse_result_s res = {0};
+ struct json_value_s *root = 0;
+ ctl_cmd cmd;
+ root = json_parse_ex(session->read_buf, session->cmd_size,
+ json_parse_flags_allow_json5, 0, 0, &res);
+ if (!root)
+ {
+ const char *msg
+ = "Failed to parse remote control cmd data json";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ {
+ bool fail = false;
+ struct json_object_s *object
+ = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ fail = !ctl_handle_json_option(option, task, &cmd);
+ if (fail)
+ break;
+ option = option->next;
+ }
+ if (fail)
+ {
+ free(root);
+ return false;
+ }
+ }
+ free(root);
+ return true;
+}
+
+bool
+ctl_server_handle_data(void *taskdata)
+{
+ wrdp_thpool_task *task = taskdata;
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ switch (session->state)
+ {
+ case ctl_session_ssl_handshake:
+ {
+ int ssl_accept_ret = 0;
+ ssl_accept_ret = SSL_accept(session->ssl);
+ if (ssl_accept_ret <= 0)
+ {
+ /* ERR_print_errors_fp(stderr); */
+ int err = SSL_get_error(
+ session->ssl, ssl_accept_ret);
+ if (err != SSL_ERROR_WANT_READ
+ && err != SSL_ERROR_WANT_WRITE)
+ {
+ char buf[64];
+ snprintf(
+ buf, 63, "SSL_accept: %d", err);
+ log_msg((const uint8_t *)buf,
+ strlen(buf), wrdp_log_level_error,
+ 0);
+ return false;
+ }
+ }
+ else
+ {
+ session->state = ctl_session_connected;
+ }
+ }
+ break;
+ case ctl_session_connected:
+ {
+ if (!(session->cmd_size_known))
+ {
+ int32_t ret = SSL_read(session->ssl,
+ &(session->cmd_size) + session->read_size,
+ 4 - session->read_size);
+ if (ret <= 0)
+ {
+ int err
+ = SSL_get_error(session->ssl, ret);
+ if (err != SSL_ERROR_WANT_READ
+ && err != SSL_ERROR_WANT_WRITE)
+ {
+ char buf[64];
+ snprintf(buf, 63,
+ "SSL_read: %d", err);
+ log_msg((const uint8_t *)buf,
+ strlen(buf),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ }
+ session->read_size += ret;
+ if (session->read_size == 4)
+ {
+ session->cmd_size_known = true;
+ session->cmd_size
+ = ntohl(session->cmd_size);
+ session->read_buf
+ = malloc(session->cmd_size);
+ if (!session->read_buf)
+ {
+ perror("malloc");
+ return false;
+ }
+ /* reset read_size */
+ session->read_size = 0;
+ }
+ }
+ else
+ {
+ int32_t ret = SSL_read(session->ssl,
+ session->read_buf + session->read_size,
+ session->cmd_size - session->read_size);
+ if (ret <= 0)
+ {
+ int err
+ = SSL_get_error(session->ssl, ret);
+ if (err != SSL_ERROR_WANT_READ
+ && err != SSL_ERROR_WANT_WRITE)
+ {
+ char buf[64];
+ snprintf(buf, 63,
+ "SSL_read: %d", err);
+ log_msg((const uint8_t *)buf,
+ strlen(buf),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ }
+ session->read_size += ret;
+ if (session->read_size == session->cmd_size)
+ {
+ ctl_handle_json_array_cmd(task);
+ /* reset cmd_size_known */
+ session->cmd_size_known = false;
+ /* reset read_size, cmd_size */
+ session->cmd_size = session->read_size
+ = 0;
+ /* free read_buf */
+ free(session->read_buf);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+void
+init_remote_control()
+{
+ init_openssl();
+ create_context();
+ configure_context(ctx);
+}
diff --git a/src/core/remote_control.h b/src/core/remote_control.h
new file mode 100644
index 0000000..0600a28
--- /dev/null
+++ b/src/core/remote_control.h
@@ -0,0 +1,36 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <openssl/ssl.h>
+
+typedef enum
+{
+ ctl_session_ssl_handshake = 0,
+ ctl_session_connected
+} ctl_session_state;
+
+typedef struct ctl_session_s
+{
+ ev_io ev_con_fd_r, ev_con_fd_w;
+ int connection_fd;
+ SSL *ssl;
+ ctl_session_state state;
+
+ char *read_buf;
+ uint32_t cmd_size, read_size;
+ bool cmd_size_known;
+} ctl_session;
+
+#include "ctl_task.h"
+
+int ctl_server_init_tcp();
+int ctl_server_init_unix();
+void init_remote_control();
+ctl_task_info *ctl_create_task(int connection_fd);
+bool ctl_server_handle_data(void *taskdata);
+void ctl_destroy_task(wrdp_thpool_task *task);
diff --git a/src/core/socket_helpers.c b/src/core/socket_helpers.c
new file mode 100644
index 0000000..2d2cdbb
--- /dev/null
+++ b/src/core/socket_helpers.c
@@ -0,0 +1,143 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include "webrdp_core_api.h"
+#include "log.h"
+
+void
+socket_make_non_block(int sock)
+{
+ int flags, r;
+ while ((flags = fcntl(sock, F_GETFL, 0)) == -1 && errno == EINTR)
+ {
+ }
+ if (flags == -1)
+ {
+ perror("fcntl");
+ exit(EXIT_FAILURE);
+ }
+ while ((r = fcntl(sock, F_SETFL, flags | O_NONBLOCK)) == -1
+ && errno == EINTR)
+ {
+ }
+ if (r == -1)
+ {
+ perror("fcntl");
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+create_listen_socket_tcp(uint32_t port)
+{
+ /* TODO: ipv6 */
+ struct addrinfo hints, *res, *rp;
+ int sfd = -1;
+ int r;
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+ {
+ char sport[10];
+ snprintf(sport, 9, "%d", port);
+ r = getaddrinfo(0, sport, &hints, &res);
+ }
+ if (r != 0)
+ {
+ uint8_t buf[64];
+ snprintf((char *)buf, 63, "getaddrinfo: %s", gai_strerror(r));
+ log_msg(
+ buf, strlen((const char *)buf), wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ for (rp = res; rp; rp = rp->ai_next)
+ {
+ int val = 1;
+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sfd == -1)
+ {
+ continue;
+ }
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val,
+ (socklen_t)sizeof(val))
+ == -1)
+ {
+ continue;
+ }
+ if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ {
+ break;
+ }
+ close(sfd);
+ }
+ freeaddrinfo(res);
+ if (listen(sfd, 16) == -1)
+ {
+ perror("listen");
+ close(sfd);
+ exit(EXIT_FAILURE);
+ }
+ return sfd;
+}
+
+int
+create_listen_socket_unix(const char *path)
+{
+ struct sockaddr_un saddr;
+ int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ bzero(&saddr, sizeof(struct sockaddr_un));
+ saddr.sun_family = AF_UNIX;
+ strcpy(saddr.sun_path, path);
+ /* this will delete file on disk, beware ) */
+ unlink(path);
+ if (bind(sfd, &saddr, sizeof(struct sockaddr_un)))
+ {
+ perror("bind");
+ exit(EXIT_FAILURE);
+ }
+ /* set permissive access to socket */
+ chmod(path, 0777);
+
+ if (listen(sfd, 16) == -1)
+ {
+ perror("listen");
+ close(sfd);
+ exit(EXIT_FAILURE);
+ }
+ return sfd;
+}
+
+int
+accept_new_connection(int socket)
+{
+ int fd = -1;
+ fd = accept(socket, NULL, NULL);
+ if (fd == -1 && errno != EINTR && errno != EAGAIN
+ && errno != EWOULDBLOCK)
+ {
+ perror("accept");
+ }
+ else
+ {
+ socket_make_non_block(fd);
+ }
+ return fd;
+}
diff --git a/src/core/socket_helpers.h b/src/core/socket_helpers.h
new file mode 100644
index 0000000..7eb04de
--- /dev/null
+++ b/src/core/socket_helpers.h
@@ -0,0 +1,12 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void socket_make_non_block(int sock);
+int create_listen_socket_tcp(uint32_t port);
+int create_listen_socket_unix(const char *path);
+int accept_new_connection(int socket);
diff --git a/src/core/task.h b/src/core/task.h
new file mode 100644
index 0000000..6677739
--- /dev/null
+++ b/src/core/task.h
@@ -0,0 +1,59 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <sys/queue.h>
+
+enum backend_setting_type_e
+{
+ setting_int,
+ setting_string
+};
+
+struct backend_setting_s
+{
+ union
+ {
+ backend_setting_int setting_int;
+ backend_setting_str setting_string;
+ };
+ enum backend_setting_type_e type;
+ SLIST_ENTRY(backend_setting_s) entries;
+};
+
+struct ws_session_list_entry_s
+{
+ ws_session *session;
+ ws_session_settings settings;
+ SLIST_ENTRY(ws_session_list_entry_s) entries;
+};
+
+typedef struct task_info_s
+{
+ /* name of backend handling this task */
+ char backend_name[64];
+
+ /* backend instance */
+ wrdp_backend_module *backend;
+
+ /* pointer to wrdp_thpool_task* used by some internals */
+ void *wrdp_thpool_task;
+
+ /* if true, all calls should return immediataely
+ * and do not touch any data */
+ bool stopped;
+
+ /* libev based session timer */
+ ev_timer *ev_timer_watcher;
+
+ /* task total run time, task idle time */
+ int64_t run_time, idle_time;
+
+ /* session related settings like timeouts */
+ ws_session_settings settings;
+
+} task_info;
diff --git a/src/core/thread_impl.c b/src/core/thread_impl.c
new file mode 100644
index 0000000..2be8ed0
--- /dev/null
+++ b/src/core/thread_impl.c
@@ -0,0 +1,387 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include "wrdp_thpool.h"
+//required to work with thpool internals
+#include "wrdp_thpool_internals.h"
+
+//required to work with ws_server internals
+#include "ws_session.h"
+#include "ws_server_internals.h"
+
+#include <webrdp_module_api.h>
+
+#include "globals.h"
+#include "task.h"
+#include "remote_control.h"
+#include "ctl_task.h"
+
+#include "rdp_backend_api.h"
+
+#include "thread_sync.h"
+#include "backend_helpers.h"
+#include "curl_helpers.h"
+
+#include "curl_helpers.h"
+
+#include "log.h"
+
+#include <errno.h>
+#include "base64_url.h"
+
+void
+ws_session_init_cb(wrdp_thpool_task *task, void *userdata)
+{
+ ws_session *session = userdata;
+ task_info *info = session->task_info;
+ info->wrdp_thpool_task = task;
+ session->wrdp_thpool_task = task;
+}
+
+void
+task_destroy_timers(wrdp_thpool_task *task)
+{
+ task_info *info;
+ if (!task)
+ {
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t
+ *)"task_destroy_timers: task is null";
+ i.level = wrdp_log_level_trace;
+ log_msg_ex(&i);
+ }
+ return;
+ }
+ info = task->userdata;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"task_destroy_timers";
+ i.level = wrdp_log_level_trace;
+ i.task_info = info;
+ log_msg_ex(&i);
+ }
+ if (!info || !info->ev_timer_watcher)
+ {
+ return;
+ }
+ if (ev_is_active(info->ev_timer_watcher))
+ {
+ ev_timer_stop(task->thread->ev_th_loop, info->ev_timer_watcher);
+ }
+ free(info->ev_timer_watcher);
+ info->ev_timer_watcher = 0;
+}
+
+static void
+task_destroy_client_connection_impl(ws_session *session)
+{
+ if (session->session_state != ws_session_error)
+ session->session_state = ws_session_ended;
+ wrdp_thpool_task *task = session->wrdp_thpool_task;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"task_destroy_client_connection";
+ i.level = wrdp_log_level_trace;
+ i.ws_session = session;
+ log_msg_ex(&i);
+ }
+ if (task)
+ {
+ ev_io_stop(task->thread->ev_th_loop, &(session->ev_con_fd_r));
+ if (ev_is_active(&(session->ev_con_fd_w)))
+ {
+ ev_io_stop(
+ task->thread->ev_th_loop, &(session->ev_con_fd_w));
+ }
+ }
+ if (session->http_state == ws_server_state_ws_running
+ && session->wslay_ctx)
+ {
+ /* send all pending messages
+ * this may block ? */
+ wslay_event_shutdown_read(session->wslay_ctx);
+ wslay_event_queue_close(session->wslay_ctx, 0, 0, 0);
+ while (wslay_event_want_write(session->wslay_ctx)
+ && !wslay_event_get_close_received(session->wslay_ctx))
+ {
+ wslay_event_send(session->wslay_ctx);
+ }
+ wslay_event_shutdown_write(session->wslay_ctx);
+ wslay_event_context_free(session->wslay_ctx);
+ session->wslay_ctx = 0;
+ }
+ close(session->connection_fd);
+ free(session->sid_base64);
+}
+
+static void
+task_destroy_server_connection(wrdp_thpool_task *task)
+{
+ task_info *info = task->userdata;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"task_destroy_server_connection";
+ i.level = wrdp_log_level_trace;
+ i.task_info = info;
+ log_msg_ex(&i);
+ }
+ if (!info)
+ {
+ return;
+ }
+ /* TODO: implement backend self-destruction based on session timeout
+ * instead */
+ if (info->backend && info->backend->callbacks_module->destroy)
+ {
+ info->backend->callbacks_module->destroy(
+ info->backend->backend_internals);
+ info->backend->callbacks_module->destroy = 0;
+ }
+ if (info->backend)
+ {
+ backend_destroy(info->backend);
+ info->backend = 0;
+ }
+}
+
+void
+task_destroy_empty(wrdp_thpool_task *task)
+{
+ task_info *info = task->userdata;
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"destroy empty task";
+ i.level = wrdp_log_level_trace;
+ i.task_info = info;
+ log_msg_ex(&i);
+ }
+ task_destroy_timers(task);
+ task_destroy_server_connection(task);
+ wrdp_thread_pool_destroy_task(task, 0);
+ free(info);
+}
+
+static void
+task_remove_session_from_list_and_destroy(void *session_)
+{
+ ws_session *session = session_;
+ task_info *info = session->task_info;
+ task_destroy_client_connection_impl(session);
+ if (!info)
+ {
+ /* in rare case this may be called when task_info not created
+ * yet */
+ return;
+ }
+ SLIST_HEAD(sessions_head, ws_session_list_entry_s) *sessions_list_head_p
+ = info->backend->sessions_list_head;
+ if (!SLIST_EMPTY(sessions_list_head_p))
+ {
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->session == session)
+ {
+ SLIST_REMOVE(sessions_list_head_p, s,
+ ws_session_list_entry_s, entries);
+ free(s);
+ break;
+ }
+ }
+ }
+ curl_list_session_destroy(session);
+
+ if (SLIST_EMPTY(sessions_list_head_p))
+ {
+ task_destroy_empty((wrdp_thpool_task *)info->wrdp_thpool_task);
+ }
+ else
+ {
+ wrdp_thread_pool_destroy_task(session->wrdp_thpool_task, 0);
+ }
+ free(session);
+}
+
+void
+task_destroy_client_connection(ws_session *session)
+{
+ if (session->token_base64)
+ {
+ uint8_t *data = 0;
+ size_t data_size
+ = curl_prepare_post_request_data(&data, session);
+ curl_request_info *request = curl_init_request(session,
+ curl_request_type_post, data, data_size, 0,
+ task_remove_session_from_list_and_destroy, session);
+ free(data);
+ curl_request(request);
+ }
+}
+
+static void
+on_task_destroy(wrdp_thpool_task *task)
+{
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"cleaning task slot";
+ i.wrdp_thpool_task = task;
+ i.level = wrdp_log_level_debug;
+ log_msg_ex(&i);
+ }
+ task_destroy_timers(task);
+ task_info *info = task->userdata;
+ if (info)
+ {
+ SLIST_HEAD(sessions_head,
+ ws_session_list_entry_s) *sessions_list_head_p
+ = info->backend->sessions_list_head;
+ if (!SLIST_EMPTY(sessions_list_head_p))
+ {
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (!s->session)
+ continue;
+ if (s->session->session_state
+ != ws_session_error)
+ s->session->session_state
+ = ws_session_ended;
+ task_destroy_client_connection(s->session);
+ }
+ }
+ while (!SLIST_EMPTY(sessions_list_head_p))
+ SLIST_REMOVE_HEAD(sessions_list_head_p, entries);
+ }
+}
+
+void
+destroy_task(task_info *t_info)
+{
+ t_info->stopped = true;
+ if (t_info->backend)
+ {
+ t_info->backend->stopped = true;
+ }
+ /* TODO:
+ * eliminate possibility of small race condition here
+ * which lead to inconsistant task count in thread of thread pool
+ * for one second
+ */
+}
+
+void
+task_timeouts_check_cb(EV_P_ ev_timer *w, int revents)
+{
+
+ task_info *info = w->data;
+ if (!info)
+ {
+ return;
+ }
+ if (info->stopped)
+ {
+ w->data = 0;
+ on_task_destroy(info->wrdp_thpool_task);
+ return;
+ }
+ if (info->settings.session_idle_timeout
+ && (info->idle_time > info->settings.session_idle_timeout))
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"Session idle timeout occured";
+ i.task_info = info;
+ i.level = wrdp_log_level_debug;
+ log_msg_ex(&i);
+ destroy_task(info);
+ }
+ if (info->settings.session_time_limit
+ && (info->run_time > info->settings.session_time_limit))
+ {
+ log_msg_info i = {0};
+ i.buf = (const uint8_t *)"Session timeout occured";
+ i.task_info = info;
+ i.level = wrdp_log_level_debug;
+ log_msg_ex(&i);
+ destroy_task(info);
+ }
+ info->run_time++;
+ info->idle_time++;
+}
+
+static void
+ws_ev_connection_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ ws_session *session = w->data;
+ if (!session)
+ {
+ return;
+ }
+ task_info *info = session->task_info;
+ if (info && info->stopped)
+ {
+ return;
+ }
+ if (!ws_server_handle_data(session))
+ {
+ w->data = 0;
+ if (session->session_state != ws_session_error)
+ session->session_state = ws_session_ended;
+ task_destroy_client_connection(session);
+ }
+}
+
+void
+ws_run_session(wrdp_thpool_task *task, void *_ws_session)
+{
+ ws_session *session = _ws_session;
+ ev_io *io = &(session->ev_con_fd_r);
+ ev_io_init(
+ io, ws_ev_connection_readable_cb, session->connection_fd, EV_READ);
+ session->ev_con_fd_r.data = session;
+ ev_io_start(task->thread->ev_th_loop, io);
+}
+
+void
+ws_stop_session(wrdp_thpool_task *current_task, void *_ws_session)
+{
+ ws_session *session = _ws_session;
+ ev_io *io = &(session->ev_con_fd_r);
+ if (ev_is_active(io))
+ {
+ ev_io_stop(current_task->thread->ev_th_loop, io);
+ }
+}
+
+static void
+ctl_ev_connection_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ if (!ctl_server_handle_data(w->data))
+ {
+ ctl_destroy_task(w->data);
+ }
+}
+
+void
+ctl_run_task(wrdp_thpool_task *task)
+{
+ ctl_task_info *info = task->userdata;
+ ctl_session *session = info->session;
+ ev_io *io = &(session->ev_con_fd_r);
+ ev_io_init(
+ io, ctl_ev_connection_readable_cb, session->connection_fd, EV_READ);
+ session->ev_con_fd_r.data = task;
+ ev_io_start(task->thread->ev_th_loop, io);
+}
diff --git a/src/core/thread_impl.h b/src/core/thread_impl.h
new file mode 100644
index 0000000..2034e74
--- /dev/null
+++ b/src/core/thread_impl.h
@@ -0,0 +1,23 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void ws_run_session(wrdp_thpool_task *task, void *user_data);
+
+void ws_stop_session(wrdp_thpool_task *current_task, void *_ws_session);
+
+void ctl_run_task(wrdp_thpool_task *task, void *user_data);
+
+void task_destroy_client_connection(ws_session *session);
+
+void destroy_task(task_info *t_info);
+
+void ws_session_init_cb(wrdp_thpool_task *task, void *user_data);
+
+void task_timeouts_check_cb(EV_P_ ev_timer *w, int revents);
+
+void task_destroy_timers(wrdp_thpool_task *task);
diff --git a/src/core/thread_sync.c b/src/core/thread_sync.c
new file mode 100644
index 0000000..19b430a
--- /dev/null
+++ b/src/core/thread_sync.c
@@ -0,0 +1,75 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "globals.h"
+#include "thread_sync.h"
+#include "thread_impl.h"
+
+static void
+remove_ws_backend_from_list(user_pool_msg *pmsg)
+{
+ if (!LIST_EMPTY(&(g_globals.backends_head)))
+ {
+ struct backend_s *t1 = LIST_FIRST(&(g_globals.backends_head));
+ while (t1)
+ {
+ if (!t1->backend || t1->backend == pmsg->backend)
+ {
+ LIST_REMOVE(t1, entries);
+ free(t1);
+ break;
+ }
+ t1 = LIST_NEXT(t1, entries);
+ }
+ }
+
+ free(pmsg->backend);
+}
+
+void
+pool_message_handler(void *user_data)
+{
+ user_pool_msg *pmsg = user_data;
+ if (!pmsg)
+ {
+ return;
+ }
+ switch (pmsg->type)
+ {
+ case msg_type_destroy_ws_backend_info:
+ {
+ remove_ws_backend_from_list(pmsg);
+ }
+ break;
+ case msg_type_backend_created:
+ {
+ struct backend_s *t;
+ t = calloc(1, sizeof(struct backend_s));
+ if (!t)
+ {
+ perror("calloc");
+ free(pmsg);
+ return;
+ }
+ t->backend = pmsg->backend;
+ LIST_INSERT_HEAD(
+ &(g_globals.backends_head), t, entries);
+ }
+ break;
+ default:
+ break;
+ }
+ free(pmsg);
+}
+
+void
+thread_message_handler(void *user_data)
+{
+}
diff --git a/src/core/thread_sync.h b/src/core/thread_sync.h
new file mode 100644
index 0000000..226e610
--- /dev/null
+++ b/src/core/thread_sync.h
@@ -0,0 +1,23 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+typedef enum
+{
+ msg_type_destroy_ws_backend_info,
+ msg_type_backend_created
+} usr_pool_msg_type;
+
+typedef struct
+{
+ usr_pool_msg_type type;
+ wrdp_backend_module *backend;
+} user_pool_msg;
+
+void pool_message_handler(void *user_data);
+
+void thread_message_handler(void *user_data);
diff --git a/src/core/utilities.c b/src/core/utilities.c
new file mode 100644
index 0000000..969e36b
--- /dev/null
+++ b/src/core/utilities.c
@@ -0,0 +1,115 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/stat.h>
+
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/evp.h>
+
+#include <openssl/sha.h>
+
+bool
+is_regular_file(const char *path)
+{
+ struct stat path_stat;
+ stat(path, &path_stat);
+ return S_ISREG(path_stat.st_mode);
+}
+
+bool
+is_directory(const char *path)
+{
+ struct stat path_stat;
+ stat(path, &path_stat);
+ return S_ISDIR(path_stat.st_mode);
+}
+
+bool
+sha1(uint8_t *dst, const uint8_t *src, size_t src_length)
+{
+ SHA_CTX ctx = {0};
+ if (!SHA1_Init(&ctx))
+ {
+ return false;
+ }
+ if (!SHA1_Update(&ctx, src, src_length))
+ {
+ return false;
+ }
+ if (!SHA1_Final(dst, &ctx))
+ {
+ return false;
+ }
+ return true;
+}
+
+void
+hex_print(uint8_t *buf, size_t buf_len)
+{
+ size_t p;
+ for (p = 0; p < buf_len; ++p)
+ {
+ if (!p)
+ {
+ printf("00: ");
+ }
+ if (p && !(p % 8))
+ {
+ printf("| ");
+ }
+ if (p && !(p % 16))
+ {
+ size_t pp = p - 16;
+ for (; pp < p; ++pp)
+ {
+ printf("%c", ((char *)(buf))[pp]);
+ }
+ printf("\n%lx: ", p);
+ }
+ printf("%x ", ((char *)(buf))[p]);
+ }
+}
+
+char
+random_ascii_character()
+{
+ /* use whole ascii table of printable characters */
+ char c;
+get_new_char:
+ c = ((rand() % 94) + 32);
+ while (!c)
+ goto get_new_char;
+ return c;
+}
+
+char *
+random_ascii_string(char *buf, const size_t len)
+{
+ size_t i = 0;
+ if (!buf)
+ {
+ return 0;
+ }
+ for (; i < len; ++i)
+ {
+ buf[i] = random_ascii_character();
+ }
+ buf[len] = 0;
+ return buf;
+}
+
+void
+random_bytes(uint8_t *buf, size_t buf_len)
+{
+ size_t i = 0;
+ for (; i < buf_len; ++i)
+ {
+ buf[i] = rand();
+ }
+}
diff --git a/src/core/utilities.h b/src/core/utilities.h
new file mode 100644
index 0000000..ee2c6eb
--- /dev/null
+++ b/src/core/utilities.h
@@ -0,0 +1,28 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+/* check if destination path is regular file */
+bool is_regular_file(const char *path);
+
+/* check if destination path is directory */
+bool is_directory(const char *path);
+
+/* calculate sha1 hash of src */
+bool sha1(uint8_t *dst, const uint8_t *src, size_t src_length);
+
+/* print hex representation of buf to stdout */
+void hex_print(const uint8_t *buf, size_t buf_len);
+
+/* get one random ascii character */
+char random_ascii_character();
+
+/* get random string of ascii characters with length of 'len'
+ * and store intopointed buffer */
+char *random_ascii_string(char *buf, const size_t buf_size);
+
+void random_bytes(uint8_t *buf, size_t buf_len);
diff --git a/src/core/wrdp_thpool.c b/src/core/wrdp_thpool.c
new file mode 100644
index 0000000..c1f8145
--- /dev/null
+++ b/src/core/wrdp_thpool.c
@@ -0,0 +1,770 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <ev.h>
+
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+
+#include "webrdp_core_api.h"
+#include "log.h"
+
+void
+wrdp_thpool_destroy(wrdp_thpool *pool)
+{
+ /* TODO: finish this */
+ /* TODO: destroy all tasks */
+ /* TODO: call per thread custom_thread_deinit in each worker thread */
+ /* NOTE: unfinished, unused */
+ uint32_t i;
+ if (!pool)
+ {
+ return;
+ }
+ if (pool->threads)
+ {
+ for (i = 0; i < pool->thread_count; ++i)
+ {
+ close(pool->threads[i].pipe_fds[0]);
+ close(pool->threads[i].pipe_fds[1]);
+ }
+ }
+ if (pool->custom_pool_destroy)
+ {
+ pool->custom_pool_destroy(pool->userdata);
+ }
+ free(pool);
+}
+
+static void *wrdp_thpool_worker_thread_loop(void *thread_);
+
+static void pipe_readable_cb(struct ev_loop *loop, ev_io *w, int revents);
+
+typedef enum
+{
+ pool_obj_pool,
+ pool_obj_thread
+} thpool_obj_type;
+
+typedef struct
+{
+ union
+ {
+ wrdp_thpool *pool;
+ wrdp_thpool_thread *thread;
+ };
+ thpool_obj_type receiver;
+} pool_receiver_ptr;
+
+typedef struct
+{
+ union
+ {
+ wrdp_thpool *pool;
+ wrdp_thpool_thread *thread;
+ };
+ thpool_obj_type sender;
+} pool_sender_ptr;
+
+wrdp_thpool *
+wrdp_thpool_create(uint16_t thread_count, uint64_t max_tasks_per_thread,
+ void (*custom_thread_init)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_thread_deinit)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_pool_create)(void *user_pool_data),
+ void (*custom_pool_destroy)(void *user_pool_data),
+ void (*pool_message_handler)(void *user_data),
+ void (*thread_message_handler)(void *user_data), struct ev_loop *loop,
+ void *user_pool_data)
+{
+ wrdp_thpool *pool = calloc(1, sizeof(wrdp_thpool));
+ if (!pool)
+ {
+ perror("calloc");
+ return 0;
+ }
+
+ pool->thread_count = thread_count;
+ pool->max_tasks = max_tasks_per_thread;
+ pool->custom_pool_destroy = custom_pool_destroy;
+ pool->custom_thread_deinit = custom_thread_deinit;
+ pool->custom_thread_init = custom_thread_init;
+ pool->userdata = user_pool_data;
+ pool->pool_message_handler = pool_message_handler;
+ pool->thread_message_handler = thread_message_handler;
+
+ if (custom_pool_create)
+ {
+ custom_pool_create(pool->userdata);
+ }
+
+ pool->tasks_per_thread = calloc(thread_count, sizeof(uint64_t));
+ if (!pool->tasks_per_thread)
+ {
+ perror("calloc");
+ goto error;
+ }
+
+ pool->threads = calloc(thread_count, sizeof(wrdp_thpool_thread));
+ if (!(pool->threads))
+ {
+ perror("calloc");
+ goto error;
+ }
+ if (pipe(pool->pipe_fds) == -1)
+ {
+ perror("pipe");
+ goto error;
+ }
+
+ /* allocate memory for threads, tasks, create threads */
+ {
+ uint32_t i;
+ for (i = 0; i < thread_count; ++i)
+ {
+ if (pipe(pool->threads[i].pipe_fds) == -1)
+ {
+ perror("pipe");
+ goto error;
+ }
+ pool->threads[i].thread_id = i;
+ pool->threads[i].tasks = calloc(
+ sizeof(wrdp_thpool_task *), max_tasks_per_thread);
+ if (!pool->threads[i].tasks)
+ {
+ perror("calloc");
+ goto error;
+ }
+ pool->threads[i].pool = pool;
+ if (pthread_create(&(pool->threads[i].thread), 0,
+ wrdp_thpool_worker_thread_loop,
+ &(pool->threads[i]))
+ != 0)
+ {
+ goto error;
+ }
+ }
+ }
+ /* attach pipe reed watcher to default event loop */
+ {
+ pool_receiver_ptr *p = calloc(1, sizeof(pool_receiver_ptr));
+ if (!p)
+ {
+ perror("calloc");
+ goto error;
+ }
+ p->receiver = pool_obj_pool;
+ p->pool = pool;
+ ev_io_init(&(pool->ev_pipe_readable), pipe_readable_cb,
+ pool->pipe_fds[0], EV_READ);
+ pool->ev_pipe_readable.data = p;
+ if (loop)
+ {
+ ev_io_start(loop, &(pool->ev_pipe_readable));
+ }
+ else
+ {
+ ev_io_start(EV_DEFAULT, &(pool->ev_pipe_readable));
+ }
+ }
+ return pool;
+error:
+ if (pool)
+ {
+ wrdp_thpool_destroy(pool);
+ }
+ return 0;
+}
+
+typedef enum
+{
+ thread_msg_task_count = 1,
+ thread_msg_push_task,
+ thread_msg_task_finished,
+ thread_msg_userdata
+} thread_msg_type;
+
+typedef struct
+{
+ thread_msg_type type;
+ union
+ {
+ wrdp_thpool_task *task;
+ uint64_t running_tasks;
+ void *user_data;
+ };
+ pool_sender_ptr sender;
+} thread_msg;
+
+static void
+send_msg(int write_fd, thread_msg *msg)
+{
+ size_t io_size = 0, left = 0, struct_size = sizeof(thread_msg);
+ left = struct_size;
+ while (left)
+ {
+ io_size
+ = write(write_fd, (char *)msg + (struct_size - left), left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg_ = "error: thpool pipe write failure";
+ perror("write");
+ log_msg((const uint8_t *)msg_, strlen(msg_),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+}
+
+bool
+wrdp_thpool_send_msg_to_thread(
+ wrdp_thpool *pool, uint32_t thread_id, void *user_data)
+{
+ size_t io_size = 0, left = 0, struct_size = sizeof(thread_msg);
+ thread_msg msg;
+ memset(&msg, 0, struct_size);
+ msg.user_data = user_data;
+ msg.type = thread_msg_userdata;
+ left = struct_size;
+ if (thread_id >= pool->thread_count)
+ {
+ return false;
+ }
+ while (left)
+ {
+ io_size = write(pool->threads[thread_id].pipe_fds[1],
+ (char *)&msg + (struct_size - left), left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg = "thpool pipe write failure";
+ perror("write");
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+ return true;
+}
+
+bool
+wrdp_thpool_send_msg_to_pool(wrdp_thpool *pool, void *user_data)
+{
+ size_t io_size = 0, left = 0, struct_size = sizeof(thread_msg);
+ thread_msg msg;
+ memset(&msg, 0, struct_size);
+ msg.user_data = user_data;
+ msg.type = thread_msg_userdata;
+ left = struct_size;
+ while (left)
+ {
+ io_size = write(pool->pipe_fds[1],
+ (char *)&msg + (struct_size - left), left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg = "thpool pipe write failure";
+ perror("write");
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+ return true;
+}
+
+static thread_msg *
+read_msg(int read_fd)
+{
+ size_t io_size = 0, struct_size = sizeof(thread_msg), left = 0;
+ void *buf = calloc(1, struct_size);
+ if (!buf)
+ {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+ left = struct_size;
+ while (left)
+ {
+ io_size = read(read_fd, buf, left);
+ if (io_size == -1
+ && (errno != EAGAIN && errno != EWOULDBLOCK
+ && errno != EINTR))
+ {
+ const char *msg = "thpool pipe read failure";
+ perror("read");
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ left -= io_size;
+ }
+ }
+ return buf;
+}
+
+static bool
+send_task_to_thread(wrdp_thpool *pool, wrdp_thpool_task *task)
+{
+ uint32_t i = 0, thread_id = 0, minimal_tasks = 0;
+
+ //find thread with minimal number of running tasks or no tasks at all
+ for (; i < pool->thread_count; ++i)
+ {
+ uint64_t running_tasks = pool->tasks_per_thread[i];
+ if (!running_tasks)
+ {
+ thread_id = i;
+ break;
+ }
+ if (!minimal_tasks)
+ {
+ minimal_tasks = running_tasks;
+ thread_id = i;
+ }
+ if (running_tasks < minimal_tasks)
+ {
+ minimal_tasks = running_tasks;
+ thread_id = i;
+ }
+ }
+ //all threads have maximum tasks
+ if (minimal_tasks >= pool->max_tasks)
+ {
+ return false;
+ }
+ {
+ thread_msg msg;
+ memset(&msg, 0, sizeof(thread_msg));
+ msg.type = thread_msg_push_task;
+ msg.task = task;
+ msg.sender.sender = pool_obj_pool;
+ msg.sender.pool = pool;
+ task->thread = &(pool->threads[thread_id]);
+ if (task->task_init_cb)
+ {
+ task->task_init_cb(task, task->userdata);
+ }
+ send_msg(pool->threads[thread_id].pipe_fds[1], &msg);
+ }
+
+ return true;
+}
+
+static bool
+send_task_to_thread_by_id(
+ wrdp_thpool *pool, wrdp_thpool_task *task, uint32_t thread_id)
+{
+ if (pool->tasks_per_thread[thread_id] >= pool->max_tasks)
+ {
+ return false;
+ }
+ else
+ {
+ thread_msg msg;
+ memset(&msg, 0, sizeof(thread_msg));
+ msg.type = thread_msg_push_task;
+ msg.task = task;
+ msg.sender.sender = pool_obj_pool;
+ msg.sender.pool = pool;
+ task->thread = &(pool->threads[thread_id]);
+ if (task->task_init_cb)
+ {
+ task->task_init_cb(task, task->userdata);
+ }
+ send_msg(pool->threads[thread_id].pipe_fds[1], &msg);
+ }
+ return true;
+}
+
+bool
+wrdp_thread_pool_add_task(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata)
+{
+ wrdp_thpool_task *task = calloc(1, sizeof(wrdp_thpool_task));
+ if (!task)
+ {
+ perror("malloc");
+ return false;
+ }
+ task->userdata = userdata;
+ task->run_task = run_task;
+ task->task_init_cb = task_init_cb;
+ if (!send_task_to_thread(pool, task))
+ {
+ goto cleanup;
+ }
+ return true;
+cleanup:
+ if (task)
+ {
+ free(task);
+ }
+ return false;
+}
+
+bool
+wrdp_thread_pool_add_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata)
+{
+ wrdp_thpool_task *task = calloc(1, sizeof(wrdp_thpool_task));
+ if (!task)
+ {
+ perror("malloc");
+ return false;
+ }
+ task->userdata = userdata;
+ task->run_task = run_task;
+ task->task_init_cb = task_init_cb;
+ if (!send_task_to_thread_by_id(pool, task, thread_id))
+ {
+ goto cleanup;
+ }
+ return true;
+cleanup:
+ if (task)
+ {
+ free(task);
+ }
+ return false;
+}
+
+bool
+wrdp_thread_pool_move_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*stop_task)(wrdp_thpool_task *current_task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ wrdp_thpool_task *current_task, void *userdata)
+{
+ wrdp_thpool_task *task = calloc(1, sizeof(wrdp_thpool_task));
+ if (!task)
+ {
+ perror("malloc");
+ return false;
+ }
+ task->userdata = userdata;
+ task->run_task = run_task;
+ task->stop_task = stop_task;
+ task->task_init_cb = task_init_cb;
+ if (stop_task)
+ {
+ stop_task(current_task, userdata);
+ }
+ wrdp_thread_pool_destroy_task(current_task, 0);
+ if (!send_task_to_thread_by_id(pool, task, thread_id))
+ {
+ goto cleanup;
+ }
+ return true;
+cleanup:
+ if (task)
+ {
+ free(task);
+ }
+ return false;
+}
+
+static void
+pipe_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ pool_receiver_ptr *p = w->data;
+ thread_msg *in_msg = 0;
+ switch (p->receiver)
+ {
+ case pool_obj_thread:
+ {
+ in_msg = read_msg(p->thread->pipe_fds[0]);
+ if (!in_msg)
+ {
+ return;
+ }
+ switch (in_msg->type)
+ {
+ case thread_msg_push_task:
+ {
+ uint32_t i;
+ bool added = false;
+ for (i = 0;
+ i < p->thread->pool->max_tasks;
+ ++i)
+ {
+ if (p->thread->tasks[i])
+ {
+ continue;
+ }
+ thread_msg out_msg;
+ memset(&out_msg, 0,
+ sizeof(thread_msg));
+ p->thread->tasks[i]
+ = in_msg->task;
+ p->thread->running_task_count++;
+ if (!p->thread->tasks[i]
+ ->run_task)
+ {
+ /* TODO: error message
+ * to log */
+ break;
+ }
+ p->thread->tasks[i]->run_task(
+ p->thread->tasks[i],
+ (p->thread->tasks[i]
+ ->userdata));
+ out_msg.type
+ = thread_msg_task_count;
+ out_msg.running_tasks
+ = p->thread
+ ->running_task_count;
+ out_msg.sender.sender
+ = pool_obj_thread;
+ out_msg.sender.thread
+ = p->thread;
+ {
+ char buf[128];
+ log_msg_info mi = {0};
+ snprintf(buf, 127,
+ "Added new task to "
+ "thread"
+ " %d slot %d",
+ p->thread
+ ->thread_id,
+ i);
+ mi.buf = (uint8_t *)buf;
+ mi.level
+ = wrdp_log_level_trace;
+ mi.wrdp_thpool_task
+ = in_msg->task;
+ log_msg_ex(&mi);
+ }
+ send_msg(p->thread->pool
+ ->pipe_fds[1],
+ &out_msg);
+ added = true;
+ break;
+ }
+ if (!added)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "Error: failed to add task "
+ "to"
+ " thread %d :"
+ "no free slots",
+ p->thread->thread_id);
+ log_msg((const uint8_t *)buf,
+ strlen(buf),
+ wrdp_log_level_error, 0);
+ }
+ }
+ break;
+ case thread_msg_task_finished:
+ {
+ thread_msg out_msg;
+ size_t i = 0;
+ bool task_found = false;
+ memset(&out_msg, 0, sizeof(thread_msg));
+ for (i = 0;
+ i < p->thread->pool->max_tasks;
+ ++i)
+ {
+ if (p->thread->tasks[i]
+ == in_msg->task)
+ {
+ task_found = true;
+ p->thread->tasks[i] = 0;
+ p->thread
+ ->running_task_count--;
+ out_msg.type
+ = thread_msg_task_count;
+ out_msg.running_tasks
+ = p->thread
+ ->running_task_count;
+ out_msg.sender.sender
+ = pool_obj_thread;
+ out_msg.sender.thread
+ = p->thread;
+ {
+ char buf[128];
+ log_msg_info mi
+ = {0};
+ snprintf(buf,
+ 127,
+ "Removed "
+ "task from "
+ "thread"
+ " %d slot "
+ "%zd",
+ p->thread
+ ->thread_id,
+ i);
+ mi.wrdp_thpool_task
+ = in_msg
+ ->task;
+ mi.level
+ = wrdp_log_level_trace;
+ mi.buf
+ = (uint8_t
+ *)
+ buf;
+ log_msg_ex(&mi);
+ }
+ free(p->thread
+ ->tasks[i]);
+ send_msg(
+ p->thread->pool
+ ->pipe_fds[1],
+ &out_msg);
+ break;
+ }
+ }
+ if (!task_found)
+ {
+ const char *msg_str
+ = "wrdp_thpool: "
+ "thread_msg_task_"
+ "finished: task not "
+ "found "
+ "in thread";
+ log_msg(
+ (const uint8_t *)msg_str,
+ strlen(msg_str),
+ wrdp_log_level_error, 0);
+ }
+ }
+ break;
+ case thread_msg_userdata:
+ {
+ if (p->thread->pool
+ ->thread_message_handler)
+ {
+ p->thread->pool
+ ->thread_message_handler(
+ in_msg->user_data);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case pool_obj_pool:
+ {
+ in_msg = read_msg(p->pool->pipe_fds[0]);
+ if (!in_msg)
+ {
+ return;
+ }
+ switch (in_msg->type)
+ {
+ case thread_msg_task_count:
+ {
+ p->pool->tasks_per_thread
+ [in_msg->sender.thread->thread_id]
+ = in_msg->sender.thread
+ ->running_task_count;
+ }
+ break;
+ case thread_msg_userdata:
+ {
+ if (p->pool->pool_message_handler)
+ {
+ p->pool->pool_message_handler(
+ in_msg->user_data);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ default:
+ break;
+ }
+ if (in_msg)
+ {
+ free(in_msg);
+ }
+}
+
+static void *
+wrdp_thpool_worker_thread_loop(void *thread_)
+{
+ wrdp_thpool_thread *thread = thread_;
+ pool_receiver_ptr *p = calloc(1, sizeof(pool_receiver_ptr));
+ if (!p)
+ {
+ perror("calloc");
+ return 0;
+ }
+ p->receiver = pool_obj_thread;
+ p->thread = thread;
+ if (thread->pool->custom_thread_init)
+ {
+ thread->pool->custom_thread_init(
+ thread->pool->userdata, thread);
+ }
+ thread->ev_th_loop = ev_loop_new(EVFLAG_AUTO);
+ ev_io_init(&(thread->ev_pipe_readable), pipe_readable_cb,
+ thread->pipe_fds[0], EV_READ);
+ thread->ev_pipe_readable.data = p;
+ ev_io_start(thread->ev_th_loop, &(thread->ev_pipe_readable));
+ ev_run(thread->ev_th_loop, 0);
+ return 0;
+}
+
+void
+wrdp_thread_pool_destroy_task(
+ wrdp_thpool_task *task, void (*on_task_destroy)(wrdp_thpool_task *task))
+{
+ thread_msg msg;
+
+ /* TODO: this should never happen, but for now just crashfix hack */
+ if (!task->thread)
+ {
+ return;
+ }
+
+ memset(&msg, 0, sizeof(thread_msg));
+ if (on_task_destroy)
+ {
+ on_task_destroy(task);
+ }
+ msg.type = thread_msg_task_finished;
+ msg.task = task;
+ msg.sender.sender = pool_obj_thread;
+ msg.sender.thread = task->thread;
+ send_msg(task->thread->pipe_fds[1], &msg);
+}
diff --git a/src/core/wrdp_thpool.h b/src/core/wrdp_thpool.h
new file mode 100644
index 0000000..d6bbe38
--- /dev/null
+++ b/src/core/wrdp_thpool.h
@@ -0,0 +1,150 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <ev.h>
+
+struct wrdp_thpool_s;
+typedef struct wrdp_thpool_s wrdp_thpool;
+
+struct wrdp_thpool_thread_s;
+typedef struct wrdp_thpool_thread_s wrdp_thpool_thread;
+
+struct wrdp_thpool_task_s;
+typedef struct wrdp_thpool_task_s wrdp_thpool_task;
+
+/* initialize thread pool */
+
+/* function called in each thread to do additional initialization
+ * of user_pool_data
+void (*custom_thread_init) (void *user_pool_data);
+*/
+
+/* function called in each thread to do additional cleanup of
+ * user_pool_data
+void (*custom_thread_deinit) (void *user_pool_data);
+*/
+
+/* function called in wrdp_thpool_create to do additional initialization
+ * of user_pool_data
+void (*custom_pool_create) (void *user_pool_data);
+*/
+
+/* function called in wrdp_thpool_destroy to do additional cleanup of
+ * user_pool_data
+void (*custom_pool_destroy) (void *user_pool_data);
+*/
+/* function called on incomming mesdage with "void *user_data" directed to
+ * pool
+void (*pool_message_handler) (void *user_data);
+ */
+
+/* function called on incomming message with "void *user_data" directed to
+ * thrad
+void (*thread_message_handler) (void *user_data);
+ */
+
+/* struct ev_loop* loop
+ * it's possible to use specified ev_loop instead of EV_DEFAULT
+ */
+
+wrdp_thpool *wrdp_thpool_create(uint16_t thread_count,
+ uint64_t max_tasks_per_thread,
+ void (*custom_thread_init)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_thread_deinit)(void *user_pool_data, wrdp_thpool_thread *t),
+ void (*custom_pool_create)(void *user_pool_data),
+ void (*custom_pool_destroy)(void *user_pool_data),
+ void (*pool_message_handler)(void *user_data),
+ void (*thread_message_handler)(void *user_data), struct ev_loop *loop,
+ void *user_pool_data);
+
+/* deinitialize thread pool */
+void wrdp_thpool_destroy(wrdp_thpool *pool);
+
+/*
+ * Add task to thread pool.
+ * task will be added to thread with minimal tasks count
+ * Not thread safe.
+ * Must be called from thread which created thread pool only.
+ *
+ * pool: fully initialized thread pool
+ * run_task: task entry point callback
+ * must not block but set libev watcher instead
+ *
+ * userdata: user specified data pointer
+ */
+bool wrdp_thread_pool_add_task(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata);
+
+/*
+ * Add task to thread pool to specified thread.
+ * task will be added to specified thread
+ * Not thread safe.
+ * Must be called from thread which created thread pool only.
+ *
+ * pool: fully initialized thread pool
+ *
+ * thread_id: id of the thread to run task in
+ *
+ * run_task: task entry point callback
+ * must not block but set libev watcher instead
+ *
+ * userdata: user specified data pointer
+ */
+bool wrdp_thread_pool_add_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ void *userdata);
+
+/*
+ * Move task to specified thread.
+ * task will be moved to specified thread
+ *
+ * (technically new task in specified thread will
+ * be created, and current task will be deleted
+ * preserving task internal state)
+ *
+ * pool: fully initialized thread pool
+ *
+ * thread_id: id of the thread to run task in
+ *
+ * run_task: task entry point callback
+ * must not block but set libev watcher instead
+ *
+ * stop_task: callback function used to prepare task for ruining in new thread
+ *
+ * userdata: user specified data pointer
+ */
+bool wrdp_thread_pool_move_task_to_thread(wrdp_thpool *pool,
+ void (*run_task)(wrdp_thpool_task *task, void *userdata),
+ void (*stop_task)(wrdp_thpool_task *current_task, void *userdata),
+ uint32_t thread_id,
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata),
+ wrdp_thpool_task *current_task, void *userdata);
+
+/*
+ * it is possible to send message with "void *user_data" to specified thread,
+ * or to pool main thread
+ * NOTE: custom message handler function must also be set during pool creation
+ */
+
+bool wrdp_thpool_send_msg_to_thread(
+ wrdp_thpool *pool, uint32_t thread_id, void *user_data);
+
+bool wrdp_thpool_send_msg_to_pool(wrdp_thpool *pool, void *user_data);
+
+/*
+ * Destroy runing task.
+ * Custom destroy is function ptr "void(*callable)(wrdp_thpool_task* task)".
+ */
+
+void wrdp_thread_pool_destroy_task(
+ wrdp_thpool_task *task, void (*on_task_destroy)(wrdp_thpool_task *task));
diff --git a/src/core/wrdp_thpool_internals.h b/src/core/wrdp_thpool_internals.h
new file mode 100644
index 0000000..b37e2d3
--- /dev/null
+++ b/src/core/wrdp_thpool_internals.h
@@ -0,0 +1,113 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include "wrdp_thpool.h"
+
+struct wrdp_thpool_task_s
+{
+
+ /* Pass any user specified data pointer. */
+ void *userdata;
+
+ //thread owning task
+ wrdp_thpool_thread *thread;
+
+ /* Task callbacks */
+
+ /* Task entry point callback */
+ void (*run_task)(wrdp_thpool_task *task, void *userdata);
+
+ /* callback used to stop task before moving to another thread */
+ void (*stop_task)(wrdp_thpool_task *task, void *userdata);
+
+ /* function called just before adding task to thread,
+ * may be used to do additional task initialization
+ * userdata is user specified data passed to "wrdp_thread_pool_add_task"
+ */
+ void (*task_init_cb)(wrdp_thpool_task *task, void *userdata);
+};
+
+struct wrdp_thpool_thread_s
+{
+
+ //per thread libev based event loop
+ struct ev_loop *ev_th_loop;
+
+ ev_io ev_pipe_readable;
+
+ pthread_t thread;
+
+ wrdp_thpool_task **tasks;
+
+ uint64_t running_task_count;
+ uint16_t thread_id;
+
+ int pipe_fds[2];
+
+ //pool owning thread
+ wrdp_thpool *pool;
+};
+
+struct wrdp_thpool_s
+{
+ wrdp_thpool_thread *threads;
+ /* worker threads count */
+ uint16_t thread_count;
+
+ /* dynamic variable holding number of threads were task count check is
+ * already done
+ */
+ uint16_t checked_threads_tasks;
+
+ /* maximum tasks per thread */
+ uint64_t max_tasks;
+
+ /* buffer to hold tasks count for each thread */
+ uint64_t *tasks_per_thread;
+
+ /* internal messageing pipe */
+ int pipe_fds[2];
+
+ ev_io ev_pipe_readable;
+
+ /* data assigned by user */
+ void *userdata;
+
+ /* additional api callbacks */
+
+ /* function called in each thread to do additional initialization
+ * of user_pool_data
+ */
+ void (*custom_thread_init)(void *user_pool_data, wrdp_thpool_thread *t);
+
+ /* function called in each thread to do additional cleanup of
+ * user_pool_data
+ */
+ void (*custom_thread_deinit)(
+ void *user_pool_data, wrdp_thpool_thread *t);
+
+ /* function called in wrdp_thpool_create to do additional initialization
+ * of user_pool_data
+ */
+ void (*custom_pool_create)(void *user_pool_data);
+
+ /* function called in wrdp_thpool_destroy to do additional cleanup of
+ * user_pool_data
+ */
+ void (*custom_pool_destroy)(void *user_pool_data);
+
+ /* function called on incomming mesdage with "void *user_data" directed
+ * to pool
+ */
+ void (*pool_message_handler)(void *user_data);
+
+ /* function called on incomming message with "void *user_data" directed
+ * to thread
+ */
+ void (*thread_message_handler)(void *user_data);
+};
diff --git a/src/core/ws_protocol.c b/src/core/ws_protocol.c
new file mode 100644
index 0000000..3bb8eba
--- /dev/null
+++ b/src/core/ws_protocol.c
@@ -0,0 +1,1206 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <ev.h>
+#include <json.h>
+#include <wslay/wslay.h>
+#include <curl/curl.h>
+
+#include <errno.h>
+#include "base64_url.h"
+
+#include <openssl/hmac.h>
+
+#include <webrdp_module_api.h>
+
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "ws_session.h"
+#include "ws_protocol.h"
+#include "globals.h"
+#include "task.h"
+#include "thread_impl.h"
+#include "json_helpers.h"
+#include "curl_helpers.h"
+#include "backend_helpers.h"
+
+#include "utilities.h"
+
+#include "log.h"
+
+static bool
+token_check(ws_session *session)
+{
+ char *token = session->token_base64;
+ size_t token_len = strlen(token), raw_token_len = 0;
+ unsigned int raw_token_signature_len = 0;
+ /* used additional 2 bytes in buffer
+ * required for base64_url_decode */
+ uint8_t *raw_token = malloc(96 + 2), *raw_token_signature = 0;
+ if (!raw_token)
+ {
+ perror("malloc");
+ goto error;
+ }
+ {
+ char buf[128];
+ snprintf(buf, 127, "recieved token: %s", token);
+ log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+ errno = base64_url_decode(
+ (uint8_t *)token, token_len, raw_token, token_len, &raw_token_len);
+ if (errno)
+ {
+ perror("token_check: base64_url_decode");
+ free(raw_token);
+ goto error;
+ }
+ if (raw_token_len != 96)
+ {
+ const char *msg = "token_check: raw_roken length != 96";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ free(raw_token);
+ goto error;
+ }
+ raw_token_signature = malloc(32);
+ if (!raw_token_signature)
+ {
+ perror("malloc");
+ free(raw_token);
+ goto error;
+ }
+ /* verify token */
+ {
+ HMAC_CTX *ctx = HMAC_CTX_new();
+ HMAC_Init_ex(ctx, g_globals.settings.secret_key_verify, 64,
+ EVP_sha256(), NULL);
+ HMAC_Update(ctx, raw_token, raw_token_len - 32);
+ HMAC_Final(ctx, raw_token_signature, &raw_token_signature_len);
+ HMAC_CTX_free(ctx);
+ if (raw_token_signature_len != 32)
+ {
+ const char *msg = "token signature validation failed"
+ " (incorrect result hash size)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ free(raw_token);
+ free(raw_token_signature);
+ goto error;
+ }
+#ifdef DEBUG
+ {
+ log_msg((const uint8_t *)"old hash", strlen("old hash"),
+ wrdp_log_level_trace, wrdp_log_flag_binary);
+ log_msg(raw_token + 64, 32, wrdp_log_level_trace,
+ wrdp_log_flag_binary);
+ log_msg((const uint8_t *)"new hash", strlen("new hash"),
+ wrdp_log_level_trace, wrdp_log_flag_binary);
+ log_msg(raw_token_signature, 32, wrdp_log_level_trace,
+ wrdp_log_flag_binary);
+ }
+#endif
+ if (memcmp(raw_token + 64, raw_token_signature, 32))
+ {
+ const char *msg
+ = "token signature validation failed (result"
+ " hash differs)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ free(raw_token);
+ free(raw_token_signature);
+ goto error;
+ }
+ }
+ /* extract and store base64-urlsafe encoded sid into session */
+ {
+ /* TODO: check if size calculation correct and optimal */
+ size_t sid_len = (4 * (64 / 3)) + 4 + 2, encoded_len = 0;
+ char *sid_base64 = calloc(sid_len, sizeof(char)), *ptr;
+ base64_url_encode(raw_token, 64, (uint8_t *)sid_base64, sid_len,
+ &encoded_len);
+ sid_base64[encoded_len] = 0;
+ ptr = realloc(sid_base64, encoded_len + 1);
+ if (!ptr)
+ {
+ //if realloc failed we still can use preveously
+ //allocated memory
+ perror("realloc");
+ }
+ else
+ {
+ sid_base64 = ptr;
+ }
+ session->sid_base64 = sid_base64;
+ }
+ /* reencrypt token */
+ {
+ HMAC_CTX *ctx = HMAC_CTX_new();
+ uint8_t resigned_raw_token[96] = {0};
+ size_t base64_len = 0;
+ HMAC_Init_ex(ctx, g_globals.settings.secret_key_sign, 64,
+ EVP_sha256(), NULL);
+ HMAC_Update(ctx, raw_token, raw_token_len - 32);
+ HMAC_Final(ctx, raw_token_signature, &raw_token_signature_len);
+ HMAC_CTX_free(ctx);
+ if (raw_token_signature_len != 32)
+ {
+ const char *msg
+ = "token signature generation failed (incorrect"
+ " result hash size)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ free(raw_token);
+ free(raw_token_signature);
+ goto error;
+ }
+ memcpy(resigned_raw_token, raw_token, 64);
+ memcpy(resigned_raw_token + 64, raw_token_signature, 32);
+ errno = base64_url_encode(
+ resigned_raw_token, 96, (uint8_t *)token, 255, &base64_len);
+ if (errno)
+ {
+ free(raw_token);
+ free(raw_token_signature);
+ perror("token_verify: base64_url_encode");
+ goto error;
+ }
+ token[base64_len] = 0;
+ }
+ session->token_verified = true;
+ free(raw_token);
+ free(raw_token_signature);
+ return true;
+error:
+ session->session_state = ws_session_error;
+ free(session->token_base64);
+ session->token_base64 = 0;
+ free(session->backend_module_name);
+ session->backend_module_name = 0;
+ return false;
+}
+
+static bool
+handle_session_setting_element(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ task_info *info = session->task_info;
+ /* TODO: move timeouts to session ?
+ * cache timeouts settings until task_info created ?
+ */
+ /* if (!strncmp(json_option->name->string, "session_time_limit",
+ json_option->name->string_size))
+ {
+ info->settings.session_time_limit =
+ json_option_extract_int64 (json_option);
+ return true;
+ }
+ else if (!strncmp(json_option->name->string,
+ "session_idle_timeout", json_option->name->string_size))
+ {
+ info->settings.session_idle_timeout =
+ json_option_extract_int64 (json_option);
+ return true;
+ }
+ else */
+ if (!strncmp(json_option->name->string, "token",
+ json_option->name->string_size))
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)json_option->value->payload;
+ char *token_base64;
+ if (!jsopt->string_size)
+ {
+ const char *msg = "zero size token received";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ token_base64 = malloc(jsopt->string_size + 1);
+ memcpy(token_base64, jsopt->string, jsopt->string_size);
+ token_base64[jsopt->string_size] = 0;
+ if (session->token_base64)
+ free(session->token_base64);
+ session->token_base64 = token_base64;
+ return true;
+ }
+ else if (!strncmp(json_option->name->string, "attach_sid",
+ json_option->name->string_size))
+ {
+ /* if (session->sid_base64)
+ {
+ free (session->sid_base64);
+ } */
+ struct json_string_s *jsopt
+ = (struct json_string_s *)json_option->value->payload;
+ char *attach_sid = malloc(jsopt->string_size + 1);
+ memcpy(attach_sid, jsopt->string, jsopt->string_size);
+ attach_sid[jsopt->string_size] = 0;
+ if (session->attach_sid_base64)
+ free(session->attach_sid_base64);
+ session->attach_sid_base64 = attach_sid;
+ return true;
+ }
+ else if (!strncmp(json_option->name->string, "proto",
+ json_option->name->string_size))
+ {
+ if (info && info->backend)
+ {
+ /* backend already created, possible by "proto" setting
+ * from web ui */
+ return true;
+ }
+ struct json_string_s *jsopt
+ = (struct json_string_s *)json_option->value->payload;
+ char *var = malloc(jsopt->string_size + 1);
+ memcpy(var, jsopt->string, jsopt->string_size);
+ var[jsopt->string_size] = 0;
+ if (session->backend_module_name)
+ free(session->backend_module_name);
+ session->backend_module_name = var;
+ return true;
+ }
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "handle_session_setting:"
+ " unhandled option: %.*s",
+ (int)(json_option->name->string_size),
+ json_option->name->string);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return false;
+}
+
+static bool
+handle_backend_setting_element(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ char option_name[json_option->name->string_size + 1];
+ strncpy(option_name, json_option->name->string,
+ json_option->name->string_size);
+ option_name[json_option->name->string_size] = 0;
+ switch (json_option->value->type)
+ {
+ case json_type_null:
+ case json_type_false:
+ {
+ if (!handle_backend_setting_int(
+ option_name, 0, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ break;
+ case json_type_true:
+ {
+ if (!handle_backend_setting_int(
+ option_name, 1, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ break;
+ case json_type_number:
+ {
+ int64_t num = json_option_extract_int64(json_option);
+ if (!handle_backend_setting_int(
+ option_name, num, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ break;
+ case json_type_string:
+ {
+ struct json_string_s *jsopt
+ = (struct json_string_s *)
+ json_option->value->payload;
+ char value[jsopt->string_size + 1];
+ strncpy(value, jsopt->string, jsopt->string_size);
+ value[jsopt->string_size] = 0;
+
+ if (!strcmp(option_name, "dtsize"))
+ {
+ /* extract resolution width and height */
+ char *ptr = strchr(value, 'x');
+ if (!ptr)
+ return false;
+ char w[jsopt->string_size],
+ h[jsopt->string_size];
+ {
+ char *ptr2 = value;
+ int i;
+ for (i = 0; ptr2 != ptr; ++i, ++ptr2)
+ {
+ w[i] = ptr2[0];
+ }
+ w[i] = 0;
+ ptr++;
+ for (i = 0; ptr[i]; ++i)
+ {
+ h[i] = ptr[i];
+ }
+ h[i] = 0;
+ {
+ if (!handle_backend_setting_int(
+ "width", atoll(w),
+ session))
+ {
+ return false;
+ }
+ if (!handle_backend_setting_int(
+ "height", atoll(h),
+ session))
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+ else
+ {
+ if (!handle_backend_setting_string(
+ option_name, value, session))
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ break;
+ default:
+ {
+ const char *msg = "handle_backend_setting:"
+ " unsupported json value type";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ break;
+ }
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "handle_backend_setting:"
+ " unhandled option: %s",
+ option_name);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return true;
+}
+
+static void
+handle_json_session_option(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_object:
+ {
+ if (memmem(json_option->name->string,
+ json_option->name->string_size,
+ "session_settings", strlen("session_settings")))
+ {
+ struct json_object_s *obj
+ = json_option->value->payload;
+ struct json_object_element_s *jsoption
+ = obj->start;
+ bool unhandled_option = false;
+ while (jsoption && !unhandled_option)
+ {
+ unhandled_option
+ = !handle_session_setting_element(
+ jsoption, session);
+ if (unhandled_option)
+ break;
+ jsoption = jsoption->next;
+ }
+ return;
+ }
+ }
+ break;
+ default:
+ {
+ const char *msg
+ = "handle_json_option: unsupported json value type"
+ " (not object)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return;
+ }
+ break;
+ }
+}
+
+static void
+handle_json_session_options(struct json_value_s *root, ws_session *session)
+{
+ struct json_object_s *object = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ handle_json_session_option(option, session);
+ option = option->next;
+ }
+}
+
+static void
+handle_json_backend_option(
+ struct json_object_element_s *json_option, ws_session *session)
+{
+ switch (json_option->value->type)
+ {
+ case json_type_object:
+ {
+ if (memmem(json_option->name->string,
+ json_option->name->string_size,
+ "backend_settings", strlen("backend_settings")))
+ {
+ struct json_object_s *obj
+ = json_option->value->payload;
+ struct json_object_element_s *jsoption
+ = obj->start;
+ bool unhandled_option = false;
+ while (jsoption && !unhandled_option)
+ {
+ unhandled_option
+ = !handle_backend_setting_element(
+ jsoption, session);
+ if (unhandled_option)
+ break;
+ jsoption = jsoption->next;
+ }
+ }
+ }
+ break;
+ default:
+ {
+ const char *msg
+ = "handle_json_option: unsupported json value type"
+ " (not object)";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ }
+ break;
+ }
+}
+
+static void
+handle_json_backend_options(struct json_value_s *root, ws_session *session)
+{
+ struct json_object_s *object = (struct json_object_s *)root->payload;
+ struct json_object_element_s *option = object->start;
+ while (option)
+ {
+ handle_json_backend_option(option, session);
+ option = option->next;
+ }
+}
+
+static bool
+ws_handle_json_settings_array(
+ char *json_buf, size_t json_buf_len, ws_session *session)
+{
+ struct json_parse_result_s res = {0};
+ struct json_value_s *root = json_parse_ex(
+ json_buf, json_buf_len, json_parse_flags_allow_json5, 0, 0, &res);
+#ifdef DEBUG
+ {
+ const size_t msg_len = json_buf_len + 256;
+ char msg[msg_len];
+ snprintf(msg, msg_len, "%s: %s\n", "received json data:\n",
+ json_buf);
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ }
+#endif
+ if (!root)
+ {
+ const char *msg = "Failed to parse auth data json";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ /* handle json data
+ * assume what this is one level array
+ * of json options for backend or session*/
+ handle_json_session_options(root, session);
+ handle_json_backend_options(root, session);
+
+ if (!session->backend_module_name)
+ {
+ free(session->token_base64);
+ session->token_base64 = 0;
+ free(root);
+ return false;
+ }
+ if (!(session->token_verified) && session->token_base64)
+ {
+ if (!token_check(session))
+ {
+ free(root);
+ return false;
+ }
+ }
+ free(root);
+ return true;
+}
+
+bool
+ws_handle_token_reply_json(
+ uint8_t *_json_buf, ws_session *session, void *userdata)
+{
+ char *json_buf = (char *)_json_buf;
+ task_info *info = 0, *old_info = session->task_info;
+ if (!ws_handle_json_settings_array(json_buf, strlen(json_buf), session))
+ {
+ return false;
+ }
+ if (!backend_get(session->backend_module_name, session))
+ {
+ free(session->backend_module_name);
+ session->backend_module_name = 0;
+ return false;
+ }
+ else
+ {
+ backend_fill_settings(session);
+ }
+ free(session->attach_sid_base64);
+ session->attach_sid_base64 = 0;
+ info = session->task_info;
+
+ if (!info->backend)
+ {
+ const char *msg = "backend type(proto)setting does not set";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ if (info == old_info
+ && !info->backend->callbacks_module->init(
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ session->session_state = ws_session_started;
+ {
+ uint8_t *data = 0;
+ size_t data_size
+ = curl_prepare_post_request_data(&data, session);
+ curl_request_info *request = curl_init_request(
+ session, curl_request_type_post, data, data_size, 0, 0, 0);
+ free(data);
+ curl_request(request);
+ }
+ return true;
+}
+
+static bool
+validate_msg_size(
+ size_t msg_size, size_t expected_size, ws_input_codes msg_code)
+{
+ if (msg_size > expected_size)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "ws_protocol: message size is: %ld, expected size"
+ " is: %ld for message type %d",
+ msg_size, expected_size, msg_code);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ else if (msg_size < expected_size)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "ws_protocol: message size is: %ld, expected size"
+ " is: %ld for message type %d",
+ msg_size, expected_size, msg_code);
+ log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0);
+ return false;
+ }
+ return true;
+}
+
+static void
+generate_random_session_sid(ws_session *session)
+{
+ size_t sid_len = (4 * (64 / 3)) + 4 + 2, encoded_len = 0;
+ char *sid_base64 = calloc(sid_len, sizeof(char)), *ptr;
+ uint8_t random_data[64];
+ random_bytes(random_data, 64);
+ base64_url_encode(
+ random_data, 64, (uint8_t *)sid_base64, sid_len, &encoded_len);
+ sid_base64[encoded_len] = 0;
+ ptr = realloc(sid_base64, encoded_len + 1);
+ if (!ptr)
+ {
+ /* if realloc failed we still can use preveously allocated
+ * memory
+ */
+ perror("realloc");
+ }
+ else
+ {
+ sid_base64 = ptr;
+ }
+ session->sid_base64 = sid_base64;
+}
+
+bool
+ws_handle_message(
+ const struct wslay_event_on_msg_recv_arg *msg, ws_session *session)
+{
+ uint32_t hcode1 = 0;
+ task_info *info = session->task_info;
+ if (msg->msg_length < 4)
+ {
+ const char *msg = "Error: websocket message is too short";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return false;
+ }
+ memcpy(&hcode1, msg->msg, 4);
+ if (session->session_state == ws_session_initial)
+ {
+ switch (hcode1)
+ {
+ case ws_in_credential_json:
+ {
+ size_t str_len = (msg->msg_length - 4) / 4;
+ char *str = malloc(str_len + 1);
+ if (!str)
+ {
+ perror("malloc");
+ return false;
+ }
+ {
+ size_t i, pos = 4;
+ for (i = 0; i < str_len; ++i, pos += 4)
+ {
+ str[i] = msg->msg[pos];
+ }
+ }
+ str[str_len] = 0;
+ if (!ws_handle_json_settings_array(
+ str, str_len, session))
+ {
+ free(str);
+ return false;
+ }
+ free(str);
+
+ /* backend and task_info created/set inside
+ * "ws_handle_json_settings_array", refresh
+ * pointer here */
+ info = session->task_info;
+
+ /* TODO: DEBUG BEGIN*/
+ //use_token = true;
+ /* DEBUG END */
+ if (!session->token_base64)
+ {
+ if (!info || !info->backend)
+ {
+ const char *msg
+ = "backend "
+ "type(proto)setting"
+ "does not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg),
+ wrdp_log_level_error, 0);
+ return false;
+ }
+ if (!info->backend->callbacks_module
+ ->init(
+ info->backend
+ ->backend_internals))
+ {
+ return false;
+ }
+ /* token is not used, we need to
+ * generate random sid */
+ generate_random_session_sid(session);
+ session->session_state
+ = ws_session_started;
+
+ if (session->token_base64)
+ {
+ uint8_t *data = 0;
+ size_t data_size
+ = curl_prepare_post_request_data(
+ &data, session);
+ curl_request_info *request
+ = curl_init_request(session,
+ curl_request_type_post,
+ data, data_size, 0, 0,
+ 0);
+ free(data);
+ curl_request(request);
+ }
+ }
+ else
+ {
+ curl_request_info *request
+ = curl_init_request(session,
+ curl_request_type_get, 0, 0,
+ ws_handle_token_reply_json, 0,
+ 0);
+ bool ok = curl_request(request);
+ if (!ok)
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ else if (session->session_state == ws_session_started)
+ {
+ switch (hcode1)
+ {
+ case ws_in_specialcomb:
+ {
+ uint32_t *hcode2 = (uint32_t *)msg->msg + 1;
+ ws_input_keycomb comb;
+ if (!info->backend->callbacks_input->kcomb)
+ break;
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(uint32_t), ws_in_specialcomb))
+ {
+ return false;
+ }
+ switch (*hcode2)
+ {
+ case 0:
+ comb
+ = ws_keycomb_ctrlaltdel_press;
+ break;
+ case 1:
+ comb = ws_keycomb_alttab_press;
+ break;
+ case 2:
+ comb
+ = ws_keycomb_alttab_release;
+ break;
+ default:
+ /* this should never happen */
+ comb
+ = ws_keycomb_ctrlaltdel_press;
+ break;
+ }
+ if (!info->backend->callbacks_input->kcomb(
+ comb, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_mouse:
+ {
+ if (!info->backend->callbacks_input->mouse)
+ break;
+ typedef struct
+ {
+ uint32_t op;
+ uint32_t flags;
+ uint32_t x;
+ uint32_t y;
+ } wsmsg;
+ if (!validate_msg_size(msg->msg_length,
+ sizeof(wsmsg), ws_in_mouse))
+ {
+ return false;
+ }
+ const wsmsg *m = (const wsmsg *)msg->msg;
+ ws_input_mouse mi;
+ mi.flags = m->flags;
+ mi.x = m->x;
+ mi.y = m->y;
+ if (!info->backend->callbacks_input->mouse(
+ mi, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_kupdown:
+ {
+ if (!info->backend->callbacks_input->kupdown)
+ break;
+ typedef struct
+ {
+ uint32_t op;
+ uint32_t down;
+ uint32_t code;
+ } wsmsg;
+ if (!validate_msg_size(msg->msg_length,
+ sizeof(wsmsg), ws_in_kupdown))
+ {
+ return false;
+ }
+ const wsmsg *m = (const wsmsg *)msg->msg;
+ ws_input_kupdown mi;
+ mi.code = m->code;
+ mi.down = m->down;
+ if (!info->backend->callbacks_input->kupdown(
+ mi, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_kpress:
+ {
+ if (!info->backend->callbacks_input->kpress)
+ break;
+ typedef struct
+ {
+ uint32_t op;
+ uint32_t shiftstate;
+ uint32_t code;
+ } wsmsg;
+ if (!validate_msg_size(msg->msg_length,
+ sizeof(wsmsg), ws_in_kpress))
+ {
+ return false;
+ }
+ const wsmsg *m = (const wsmsg *)msg->msg;
+ ws_input_kpress mi;
+ mi.code = m->code;
+ mi.shiftstate = m->shiftstate;
+ if (!info->backend->callbacks_input->kpress(
+ mi, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_unicode:
+ {
+ if (!info->backend->callbacks_input->unicode)
+ break;
+ ws_input_unicode mu;
+ mu.length = msg->msg_length - 4;
+ mu.input = (const uint32_t *)msg->msg + 4;
+ if (!info->backend->callbacks_input->unicode(
+ mu, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_clipbrd_data_request:
+ {
+ if (!info->backend->callbacks_clipbrd
+ ->request_data)
+ {
+ const char *msg
+ = "wrdp_backend_cb_clipboard->"
+ "request_data"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(uint8_t),
+ ws_in_clipbrd_data_request))
+ {
+ return false;
+ }
+ wrdp_backend_clipbrd_data_request req;
+ req.format = *((uint8_t *)(msg->msg + 4));
+ if (!info->backend->callbacks_clipbrd
+ ->request_data(&req,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_clipbrd_changed:
+ {
+ if (!info->backend->callbacks_clipbrd
+ ->data_changed)
+ {
+ const char *msg
+ = "wrdp_backend_cb_clipboard->"
+ "data_changed not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ wrdp_backend_clipbrd_fmts fmts;
+ fmts.count = msg->msg_length - 4;
+ fmts.formats = (uint8_t *)msg->msg + 4;
+ if (!info->backend->callbacks_clipbrd
+ ->data_changed(&fmts,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_clipbrd_data:
+ {
+ if (!info->backend->callbacks_clipbrd
+ ->send_data)
+ {
+ const char *msg = "wrdp_backend_cb_"
+ "clipboard->send_data"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ wrdp_backend_clipbrd_data data;
+ data.size = msg->msg_length - 4;
+ data.data = (uint8_t *)msg->msg + 4;
+ if (!info->backend->callbacks_clipbrd
+ ->send_data(&data,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_ft_request:
+ {
+ if (!info->backend->callbacks_ft->request)
+ {
+ const char *msg
+ = "wrdp_backend_cb_filetransfer->"
+ "request not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if ((msg->msg_length - 4) < sizeof(uint16_t))
+ {
+ const char *msg
+ = "ws_protocol: ws_in_ft_request "
+ "message"
+ " is too small";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error,
+ 0);
+ return false;
+ }
+ wrdp_backend_ft_file_request req;
+ if (!validate_msg_size(msg->msg_length - 4,
+ 4 + 8 + 8, ws_in_ft_request))
+ {
+ return false;
+ }
+ memcpy(&(req.file_id), msg->msg + 4, 4);
+ memcpy(&(req.req_size), msg->msg + 4 + 4, 8);
+ memcpy(&(req.file_offset), msg->msg + 4 + 4 + 8,
+ 8);
+ if (!info->backend->callbacks_ft->request(
+ &req, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_ft_chunk:
+ {
+ if (!info->backend->callbacks_ft->chunk)
+ {
+ const char *msg = "wrdp_backend_cb_"
+ "filetransfer->chunk"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if ((msg->msg_length - 4)
+ < sizeof(wrdp_backend_ft_chunk))
+ {
+ const char *msg
+ = "ws_protocol: ws_in_ft_chunk "
+ "message"
+ " is too small";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error,
+ 0);
+ return false;
+ }
+ const wrdp_backend_ft_chunk *c
+ = (wrdp_backend_ft_chunk *)(msg->msg + 4);
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(wrdp_backend_ft_chunk) + c->size,
+ ws_in_ft_chunk))
+ {
+ return false;
+ }
+ const uint8_t *data = (const uint8_t
+ *)(msg->msg + 4
+ + sizeof(wrdp_backend_ft_chunk));
+ if (!info->backend->callbacks_ft->chunk(c, data,
+ info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_in_ft_finished:
+ {
+ if (!info->backend->callbacks_ft->finish)
+ {
+ const char *msg = "wrdp_backend_cb_"
+ "filetransfer->finish"
+ " not set";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning,
+ 0);
+ break;
+ }
+ if (!validate_msg_size(msg->msg_length - 4,
+ sizeof(wrdp_backend_ft_status),
+ ws_in_ft_finished))
+ {
+ return false;
+ }
+ const wrdp_backend_ft_status *f
+ = (wrdp_backend_ft_status *)(msg->msg + 4);
+ if (!info->backend->callbacks_ft->finish(
+ f, info->backend->backend_internals))
+ {
+ return false;
+ }
+ }
+ break;
+ default:
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "protocol error, unsupported message code"
+ " %d",
+ hcode1);
+ log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+static void
+ev_con_w_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ ws_session *s = w->data;
+ if (wslay_event_want_write(s->wslay_ctx))
+ {
+ wslay_event_send(s->wslay_ctx);
+ }
+ if (!wslay_event_want_write(s->wslay_ctx))
+ {
+ ev_io_stop(loop, w);
+ }
+}
+
+static void
+ws_send(const uint8_t *buf, size_t buf_size, uint8_t opcode, void *_ws_session)
+{
+ ws_session *session = _ws_session;
+ task_info *info = session->task_info;
+ wrdp_thpool_task *t = info->wrdp_thpool_task;
+ struct wslay_event_msg msgarg;
+ if (info->stopped)
+ {
+ return;
+ }
+ msgarg.opcode = opcode;
+ msgarg.msg = buf;
+ msgarg.msg_length = buf_size;
+ wslay_event_queue_msg(session->wslay_ctx, &msgarg);
+ if (wslay_event_want_write(session->wslay_ctx)
+ && !ev_is_active(&(session->ev_con_fd_w)))
+ {
+ ev_io *io = &(session->ev_con_fd_w);
+ ev_io_init(io, ev_con_w_cb, session->connection_fd, EV_WRITE);
+ io->data = session;
+ ev_io_start(t->thread->ev_th_loop, io);
+ }
+}
+
+static void
+ws_send_impl(
+ const uint8_t *buf, size_t buf_size, void *_task_info, uint8_t type)
+{
+ task_info *info = _task_info;
+ SLIST_HEAD(sessions_head, ws_session_list_entry_s) *sessions_list_head_p
+ = info->backend->sessions_list_head;
+ if (!SLIST_EMPTY(sessions_list_head_p))
+ {
+ for (struct ws_session_list_entry_s *s
+ = SLIST_FIRST(sessions_list_head_p);
+ s; s = SLIST_NEXT(s, entries))
+ {
+ if (s->session)
+ ws_send(buf, buf_size, type, s->session);
+ }
+ }
+}
+
+void
+ws_send_text(const uint8_t *buf, size_t buf_size, void *_task_info)
+{
+ ws_send_impl(buf, buf_size, _task_info, 1);
+}
+
+void
+ws_send_binary(const uint8_t *buf, size_t buf_size, void *_task_info)
+{
+ ws_send_impl(buf, buf_size, _task_info, 2);
+}
+
+uint8_t *
+ws_pack_msg(const uint8_t *buf, size_t buf_size, uint32_t msg_code)
+{
+ /* TODO: avoid copying data somehow */
+ uint8_t *msg = malloc(buf_size + 4);
+ if (!msg)
+ {
+ /* TODO: handle error */
+ perror("malloc");
+ return 0;
+ }
+ memcpy(msg, &msg_code, 4);
+ if (buf)
+ {
+ memcpy(msg + 4, buf, buf_size);
+ }
+ return msg;
+}
diff --git a/src/core/ws_protocol.h b/src/core/ws_protocol.h
new file mode 100644
index 0000000..d961fa2
--- /dev/null
+++ b/src/core/ws_protocol.h
@@ -0,0 +1,79 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+/**
+ * OP-Codes, sent from the (JavaScript)
+ * client to the server.
+ */
+typedef enum
+{
+ ws_in_mouse = 0, /* input is ws_input_mouse */
+ ws_in_kupdown, /* input is ws_input_kupdown */
+ ws_in_kpress, /* input is uint32_t code */
+ ws_in_specialcomb,
+ ws_in_credential_json,
+ ws_in_unicode, /* input is wchar_t* string stored into uint32_t array */
+
+ /* DRAFT */
+ ws_in_clipbrd_changed, /* message with new clipboard information */
+ ws_in_clipbrd_data, /* message with clipboard data in requested fromat
+ */
+ ws_in_clipbrd_data_request, /* message containing clipboarddata request
+ * of specified format */
+ ws_in_ft_request, /* request filetransfer */
+ ws_in_ft_chunk, /* filetransfer chunk from client */
+ ws_in_ft_finished, /* filetransfer from client finished */
+ /* last */
+ ws_in_unused
+} ws_input_codes;
+
+typedef enum
+{
+ ws_out_beginpaint = 0,
+ ws_out_endpaint,
+ ws_out_bitmap,
+ ws_out_opaquerect,
+ ws_out_setbounds,
+ ws_out_patblt,
+ ws_out_multi_opaquerect,
+ ws_out_scr_btl,
+ ws_out_ptr_new,
+ ws_out_ptr_free,
+ ws_out_ptr_set,
+ ws_out_ptr_set_null,
+ ws_out_ptr_set_default,
+ /* DRAFT! */
+ ws_out_clpbrd_changed,
+ ws_out_clpbrd_data,
+ ws_out_clpbrd_request_data,
+ ws_out_ft_request,
+ ws_out_ft_chunk,
+ ws_out_ft_finish,
+ /* last */
+ ws_out_last
+} ws_output_codes;
+
+bool ws_handle_message(
+ const struct wslay_event_on_msg_recv_arg *msg, ws_session *session);
+
+/* send text websocket message */
+void ws_send_text(const uint8_t *buf, size_t buf_size, void *_task_info);
+
+/* send binary websocket message */
+void ws_send_binary(const uint8_t *buf, size_t buf_size, void *_task_info);
+
+/* allocate new buffer of size msg_len + 4 bytes,
+ * prepend msg_code to msg_data
+ */
+uint8_t *ws_pack_msg(const uint8_t *buf, size_t buf_size, uint32_t msg_code);
+
+/* handle json response from external auth server */
+bool ws_handle_token_reply_json(
+ uint8_t *json_buf, ws_session *session, void *userdata);
diff --git a/src/core/ws_server_internals.h b/src/core/ws_server_internals.h
new file mode 100644
index 0000000..59842d3
--- /dev/null
+++ b/src/core/ws_server_internals.h
@@ -0,0 +1,7 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once \ No newline at end of file
diff --git a/src/core/ws_session.c b/src/core/ws_session.c
new file mode 100644
index 0000000..276ca9a
--- /dev/null
+++ b/src/core/ws_session.c
@@ -0,0 +1,636 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <fcntl.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+/*
+ * HTTP and Websocket serialization/deserializtion headers
+ */
+
+#include <picohttpparser.h>
+#include <utils/base64.h>
+
+#include <webrdp_module_api.h>
+
+#include "globals.h"
+#include "ws_session.h"
+#include "ws_server_internals.h"
+#include "ws_protocol.h"
+#include "thread_impl.h"
+#include "utilities.h"
+#include "wrdp_thpool.h"
+#include "wrdp_thpool_internals.h"
+#include "task.h"
+#include "socket_helpers.h"
+
+#include "log.h"
+
+static int ws_server_socket = -1;
+static int ws_server_socket_unix = -1;
+
+int
+ws_server_init()
+{
+ if (g_globals.settings.ws_port <= 0)
+ {
+ ws_server_socket = -1;
+ return ws_server_socket;
+ }
+ ws_server_socket = create_listen_socket_tcp(g_globals.settings.ws_port);
+ if (ws_server_socket != -1)
+ {
+ socket_make_non_block(ws_server_socket);
+ }
+ return ws_server_socket;
+}
+
+int
+ws_server_init_unix()
+{
+ if (!g_globals.settings.ws_socket_path
+ || !g_globals.settings.ws_socket_path[0])
+ {
+ ws_server_socket_unix = -1;
+ return ws_server_socket_unix;
+ }
+ ws_server_socket_unix
+ = create_listen_socket_unix(g_globals.settings.ws_socket_path);
+
+ if (ws_server_socket_unix != -1)
+ {
+ socket_make_non_block(ws_server_socket_unix);
+ }
+ return ws_server_socket_unix;
+}
+
+static bool
+check_http_header_value_no_case(const struct phr_header *hdr, const char *value)
+{
+ if (hdr->value_len != strlen(value))
+ return false;
+ if (!strncasecmp(hdr->value, value, strlen(value)))
+ return true;
+ return false;
+}
+
+/*static bool
+check_http_header_value(const struct phr_header *hdr, const char *value)
+{
+ if (memmem(hdr->value, hdr->value_len, value, strlen(value)))
+ return true;
+ return false;
+}*/
+
+static struct phr_header *
+find_http_header(
+ struct phr_header *headers, size_t num_headers, const char *header_name)
+{
+ size_t i;
+ for (i = 0; i != num_headers; ++i)
+ {
+ if (!strncmp(headers[i].name, header_name, headers[i].name_len))
+ return &headers[i];
+ }
+ return 0;
+}
+
+#ifdef DEBUG
+
+static void
+print_http_header(const struct phr_header header, char *buf)
+{
+ char msg_buf[4096] = {0};
+ char name_buf[1024] = {0}, value_buf[3072] = {0};
+ memcpy(name_buf, header.name, header.name_len);
+ memcpy(value_buf, header.value, header.value_len);
+ snprintf(msg_buf, 4095, "\n%s: %s", name_buf, value_buf);
+ strcat(buf, msg_buf);
+}
+
+static void
+print_http_headers(struct phr_header *headers, size_t num_headers)
+{
+ size_t i;
+ char msg[81920] = {0};
+ strcpy(msg, "http headers:\n");
+ for (i = 0; i != num_headers; ++i)
+ {
+ print_http_header(headers[i], msg);
+ }
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+}
+
+#endif
+
+typedef struct
+{
+ struct phr_header *hdr_key, *hdr_ext;
+ const char *path;
+ size_t path_len;
+} http_get_request_info;
+
+#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+static int
+create_accept_key(uint8_t *dst, const char *client_key, size_t *base64_len)
+{
+ uint8_t sha1buf[20], key_src[60];
+ memcpy(key_src, client_key, 24);
+ memcpy(key_src + 24, WS_GUID, 36);
+ if (!sha1(sha1buf, key_src, sizeof(key_src)))
+ return false;
+ return base64_encode(sha1buf, 20, dst, 60, base64_len);
+}
+
+typedef struct
+{
+ size_t pos;
+ /* Should not be touched by user */
+ ev_io *socket_fd_writeable;
+ bool can_write;
+} ws_s_a_s_i_internals;
+
+typedef struct
+{
+ /* initialized task info */
+ ws_session *session;
+ /* user allocated buffer filled with data for sending via socket
+ * freed by function */
+ void *buf;
+ /* buffer size */
+ size_t buf_size;
+ /* initialzed connection socket returned by accept
+ * set in non-blocking mode */
+ int socket_fd;
+
+ void (*write_done_cb)(ws_session *session, bool success);
+
+ ws_s_a_s_i_internals internals;
+} ws_server_async_send_info;
+
+static void
+ev_connection_writeable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ ws_server_async_send_info *w_info = w->data;
+ ssize_t len = 0;
+ if ((len = send(w_info->socket_fd, w_info->buf + w_info->internals.pos,
+ w_info->buf_size - w_info->internals.pos, MSG_DONTWAIT))
+ == -1)
+ {
+ if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
+ {
+ perror("send");
+ /* TODO: handle send error */
+ w_info->write_done_cb(w_info->session, false);
+ goto cleanup;
+ }
+ }
+ w_info->internals.pos += len;
+ if (w_info->internals.pos == w_info->buf_size)
+ {
+ /* Writing done.
+ * Doing cleanup. */
+ w_info->write_done_cb(w_info->session, true);
+ goto cleanup;
+ }
+ return;
+cleanup:
+ ev_io_stop(loop, w);
+ free(w); /* NOTE: this is "info->internals.socket_fd_writeable" */
+ free(w_info->buf);
+ free(w_info);
+}
+
+/* ws_server_async_send_info *info must be allocated by caller
+ * will be freed by function */
+static bool
+ws_server_async_send(ws_server_async_send_info *info)
+{
+ info->internals.socket_fd_writeable = calloc(1, sizeof(ev_io));
+ wrdp_thpool_task *task = info->session->wrdp_thpool_task;
+ if (!info->internals.socket_fd_writeable)
+ {
+ /* TODO: handle allocation error */
+ free(info->buf);
+ perror("calloc");
+ return false;
+ }
+ ev_io_init(info->internals.socket_fd_writeable,
+ ev_connection_writeable_cb, info->socket_fd, EV_WRITE);
+ info->internals.socket_fd_writeable->data = info;
+ ev_io_start(
+ task->thread->ev_th_loop, info->internals.socket_fd_writeable);
+ return true;
+}
+
+static ssize_t
+ws_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len,
+ int flags, void *user_data)
+{
+ ws_session *session = user_data;
+ ssize_t r = 0;
+ int sflags = MSG_DONTWAIT;
+ r = send(session->connection_fd, data, len, sflags);
+ if (r == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
+ }
+ else
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
+ }
+ }
+ return r;
+}
+
+static ssize_t
+ws_recv_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len,
+ int flags, void *user_data)
+{
+ ws_session *session = user_data;
+ ssize_t r;
+ r = recv(session->connection_fd, buf, len, MSG_DONTWAIT);
+ if (r == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
+ }
+ else
+ {
+ wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
+ task_destroy_client_connection(session);
+ }
+ }
+ else if (r == 0)
+ {
+ /* Unexpected EOF is also treated as an error */
+ wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
+ r = -1;
+ }
+ return r;
+}
+
+static void
+ws_on_msg_recv_callback(wslay_event_context_ptr ctx,
+ const struct wslay_event_on_msg_recv_arg *arg, void *_ws_session)
+{
+ if (!wslay_is_ctrl_frame(arg->opcode))
+ {
+ if (!ws_handle_message(arg, _ws_session))
+ {
+ log_msg_info i;
+ i.ws_session = _ws_session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t
+ *)"Failed to handle websocket message.";
+ log_msg_ex(&i);
+ }
+ }
+}
+
+void
+ws_server_websocket_init(ws_session *session)
+{
+ struct wslay_event_callbacks callbacks = {ws_recv_callback,
+ ws_send_callback, NULL, NULL, NULL, NULL, ws_on_msg_recv_callback};
+ /* TODO: set TCP_NODELAY only for tcp socket, and not for unix */
+ /* int val = 1;
+
+ if (setsockopt (server->connection_fd,
+ IPPROTO_TCP,
+ TCP_NODELAY,
+ &val,
+ (socklen_t)sizeof (val)) == -1)
+ {
+ perror ("setsockopt: TCP_NODELAY");
+ thr_destroy_task (task);
+ return;
+ } */
+ wslay_event_context_server_init(
+ &(session->wslay_ctx), &callbacks, session);
+}
+
+static void
+ws_server_send_http_reply_w_cb(ws_session *session, bool success)
+{
+ if (!success)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: Failed to send reply "
+ "with http protocol upgrade.";
+ log_msg_ex(&i);
+ /* TODO: handle error */
+ return;
+ }
+ session->http_state = ws_server_state_ws_running;
+ ws_server_websocket_init(session);
+}
+
+static void
+ws_server_send_http_reply(http_get_request_info info, ws_session *session)
+{
+ char accept_key[60] = {0}, res_header[255] = {0};
+ int header_len = 0;
+ size_t base64_len = 0 /*, written = 0*/;
+ create_accept_key(
+ (uint8_t *)accept_key, info.hdr_key->value, &base64_len);
+ header_len = snprintf(res_header, sizeof(res_header),
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: %.*s\r\n"
+ "\r\n",
+ (int)base64_len, accept_key);
+ {
+ ws_server_async_send_info *info
+ = calloc(1, sizeof(ws_server_async_send_info));
+ if (!info)
+ {
+ /* TODO: handle allocation error */
+ perror("calloc");
+ return;
+ }
+ info->buf = strdup(res_header);
+ info->buf_size = header_len;
+ info->socket_fd = session->connection_fd;
+ info->write_done_cb = ws_server_send_http_reply_w_cb;
+ info->session = session;
+ /* here is no memory leak, info is freed by ws_server_async_send
+ */
+ ws_server_async_send(info);
+ }
+}
+
+static bool
+ws_server_handle_http_get(const char *path, size_t path_len,
+ struct phr_header *headers, size_t num_headers, ws_session *session)
+{
+ struct phr_header *hdr_ws_key
+ = find_http_header(headers, num_headers, "Sec-WebSocket-Key"),
+ /* *hdr_connection = 0, */ *hdr_upgrade = 0, *hdr_ws_ext = 0;
+#ifdef DEBUG
+ print_http_headers(headers, num_headers);
+#endif
+ if (!hdr_ws_key)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: error: Sec-WebSocket-Key "
+ "header not found";
+ log_msg_ex(&i);
+ return false;
+ }
+ if (hdr_ws_key->value_len != 24)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: error: Sec-WebSocket-Key "
+ "header value length != 24";
+ log_msg_ex(&i);
+ return false;
+ }
+ /* hdr_connection = find_http_header (headers, num_headers,
+ "Connection"); if (!hdr_connection) return false; if
+ (!check_http_header_value (hdr_connection, "Upgrade")) return
+ false;*/
+ hdr_upgrade = find_http_header(headers, num_headers, "Upgrade");
+ if (!hdr_upgrade)
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t
+ *)"ws_session: error: Upgrade header not found";
+ log_msg_ex(&i);
+ return false;
+ }
+ if (!check_http_header_value_no_case(hdr_upgrade, "websocket"))
+ {
+ log_msg_info i;
+ i.ws_session = session;
+ i.level = wrdp_log_level_warning;
+ i.buf = (const uint8_t *)"ws_session: error: Upgrade header "
+ "value != websocket";
+ log_msg_ex(&i);
+ return false;
+ }
+ hdr_ws_ext = find_http_header(
+ headers, num_headers, "Sec-WebSocket-Extensions");
+ http_get_request_info info = {0};
+ info.hdr_key = hdr_ws_key;
+ info.hdr_ext = hdr_ws_ext;
+ info.path = path;
+ info.path_len = path_len;
+ ws_server_send_http_reply(info, session);
+
+ return true;
+}
+
+static bool
+ws_server_socket_read(void *taskdata)
+{
+ ws_session *session = taskdata;
+ ssize_t read_size = 0;
+
+ /* int err = 0;
+ socklen_t slen = sizeof (err);
+ getsockopt (socket, SOL_SOCKET, SO_ERROR, &err, &slen);
+ if(err)
+ return false; */
+ session->prev_read_size = session->read_size;
+ while (
+ (read_size = recv(session->connection_fd,
+ session->read_buf + session->read_size,
+ sizeof(session->read_buf) - session->read_size, MSG_DONTWAIT))
+ == -1
+ && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
+ {
+ }
+ if (read_size == -1)
+ {
+ perror("recv");
+ goto error;
+ }
+ if (!read_size)
+ {
+ const char *msg = "remote host closed connection";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_debug, 0);
+ goto cleanup;
+ }
+ session->read_size += read_size;
+ return true;
+error:
+{
+ const char *msg = "connection error occurs, destroying task";
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+}
+cleanup:
+ return false;
+}
+
+static bool
+ws_server_handle_http_request(ws_session *session)
+{
+ char *method, *path;
+ int pret, minor_version;
+ const short header_count = 256;
+ struct phr_header headers[header_count];
+ size_t method_len, path_len, num_headers = header_count;
+#ifdef DEBUG
+ {
+ const char *msg_base = "RAW HTTP request:\n";
+ const size_t buf_size
+ = strlen(msg_base) + session->read_size + 1;
+ log_msg_info i = {0};
+ uint8_t *msg = malloc(buf_size);
+ strcpy((char *)msg, msg_base);
+ strcat((char *)msg, session->read_buf);
+ i.buf_size = buf_size;
+ i.ws_session = session;
+ i.buf = msg;
+ i.level = wrdp_log_level_trace;
+ log_msg_ex(&i);
+ free(msg);
+ }
+#endif
+
+ /* parse the request */
+ pret = phr_parse_request((const char *)session->read_buf,
+ session->read_size, (const char **)&method, &method_len,
+ (const char **)&path, &path_len, &minor_version, headers,
+ &num_headers, session->prev_read_size);
+ if (pret == 0)
+ {
+ if (session->read_size == sizeof(session->read_buf))
+ {
+ const char *msg = "HTTP request is too large";
+ log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto error;
+ }
+ return true; /* need more data */
+ }
+ else if (pret == -1)
+ {
+ const char *msg = "Failed to parse HTTP request";
+ log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ /* Print request */
+ /* printf("request is %d bytes long\n", pret);
+ printf("method is %.*s\n", (int)method_len, method);
+ printf("path is %.*s\n", (int)path_len, path);
+ printf("HTTP version is 1.%d\n", minor_version);
+ printf("headers:\n");
+ {
+ int i;
+ for (i = 0; i != num_headers; ++i)
+ {
+ printf("%.*s: %.*s\n",
+ (int)headers[i].name_len, headers[i].name, (int)headers[i].value_len,
+ headers[i].value);
+ }
+ } */
+ session->prev_read_size = session->read_size = 0;
+ if (!strncmp(method, "GET", method_len))
+ {
+ if (!ws_server_handle_http_get(
+ path, path_len, headers, num_headers, session))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "Unsupported HTTP method %.*s, destroying task",
+ (int)method_len, method);
+ log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0);
+ goto error;
+ }
+ /* reset buffer state */
+
+ return true;
+error:
+{
+ const char *msg = "HTTP error occurs, destroying task";
+ log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+}
+ return false;
+}
+
+bool
+ws_server_handle_data(ws_session *session)
+{
+ task_info *info;
+ info = session->task_info;
+ if (info && info->stopped)
+ {
+ return false;
+ }
+ switch (session->http_state)
+ {
+ case ws_server_state_http_handshake:
+ {
+ ws_server_socket_read(session);
+ if (!ws_server_handle_http_request(session))
+ {
+ return false;
+ }
+ }
+ break;
+ case ws_server_state_ws_running:
+ {
+ if (!wslay_event_want_read(session->wslay_ctx))
+ {
+ if (!wslay_event_get_close_received(
+ session->wslay_ctx))
+ {
+ const char *msg = "Unexpected data";
+ log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error,
+ 0);
+ ws_server_socket_read(session);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (wslay_event_recv(session->wslay_ctx))
+ {
+ return false;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
diff --git a/src/core/ws_session.h b/src/core/ws_session.h
new file mode 100644
index 0000000..0e8f95d
--- /dev/null
+++ b/src/core/ws_session.h
@@ -0,0 +1,59 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <ev.h>
+#include <wslay/wslay.h>
+
+#include <sys/queue.h>
+
+typedef enum ws_server_state_e
+{
+ ws_server_state_http_handshake = 0,
+ ws_server_state_ws_running
+} ws_server_state;
+
+typedef enum
+{
+ ws_session_initial,
+ ws_session_approved,
+ ws_session_denied,
+ ws_session_started,
+ ws_session_ended,
+ ws_session_error
+} ws_session_state;
+
+typedef struct
+{
+ /* set session time limit and session idle timeout */
+ int64_t session_time_limit, session_idle_timeout;
+} ws_session_settings;
+
+typedef struct ws_session_s
+{
+ ws_server_state http_state;
+ ws_session_state session_state;
+ ev_io ev_con_fd_r, ev_con_fd_w;
+ int connection_fd;
+ char read_buf[2048], *sid_base64, *attach_sid_base64, *token_base64,
+ *backend_module_name;
+ size_t read_size, prev_read_size;
+ wslay_event_context_ptr wslay_ctx;
+ bool token_verified;
+ void *task_info, *wrdp_thpool_task, *curlm;
+
+ /* backend settings cache */
+ SLIST_HEAD(settings_head, backend_setting_s) backend_settings_head;
+
+ SLIST_HEAD(curl_head, curls_easy_s) curls_easy_head;
+} ws_session;
+
+int ws_server_init();
+
+int ws_server_init_unix();
+
+bool ws_server_handle_data(ws_session *session);
diff --git a/src/rdp/.clang-format b/src/rdp/.clang-format
new file mode 120000
index 0000000..2d11237
--- /dev/null
+++ b/src/rdp/.clang-format
@@ -0,0 +1 @@
+../../.clang-format \ No newline at end of file
diff --git a/src/rdp/CMakeLists.txt b/src/rdp/CMakeLists.txt
new file mode 100644
index 0000000..29e7b9a
--- /dev/null
+++ b/src/rdp/CMakeLists.txt
@@ -0,0 +1,82 @@
+# -*- CMakeLists.txt generated by CodeLite IDE. Do not edit by hand -*-
+
+cmake_minimum_required(VERSION 2.8.11)
+
+project(rdp)
+
+# Define some variables
+set(PROJECT_rdp_PATH "${CMAKE_CURRENT_LIST_DIR}")
+set(WORKSPACE_PATH "${CMAKE_CURRENT_LIST_DIR}/..")
+
+
+
+#{{{{ User Code 1
+# Place your code here
+#}}}}
+
+include_directories(
+ .
+ ./include
+ ../../3rdparty/FreeRDP/include
+ ../../3rdparty/FreeRDP/winpr
+ ../../3rdparty/FreeRDP/winpr/include
+ ../../3rdparty/FreeRDP/build/winpr/include
+ ../../3rdparty/FreeRDP/build/include
+ ../../3rdparty/libev
+ ../core/include
+)
+
+
+# Compiler options
+add_definitions(-Wall)
+add_definitions(-D_XOPEN_SOURCE=500)
+add_definitions(-D_POSIX_C_SOURCE=200112L)
+
+# Linker options
+
+
+if(WIN32)
+ # Resource options
+endif(WIN32)
+
+# Library path
+set(CMAKE_LDFLAGS "${CMAKE_LDFLAGS} -L. ")
+
+# Define the C sources
+set ( C_SRCS
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_module.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_impl.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_png.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_settings.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_channels.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_display_output.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_clipboard.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_ft.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_user_input.c
+ ${CMAKE_CURRENT_LIST_DIR}/rdp_rail.c
+)
+
+set_source_files_properties(
+ ${C_SRCS} PROPERTIES COMPILE_FLAGS
+ " -Wall -D_XOPEN_SOURCE=500 -D_POSIX_C_SOURCE=200112L -fPIC")
+
+if(WIN32)
+ enable_language(RC)
+ set(CMAKE_RC_COMPILE_OBJECT
+ "<CMAKE_RC_COMPILER> ${RC_OPTIONS} -O coff -i <SOURCE> -o <OBJECT>")
+endif(WIN32)
+
+
+
+#{{{{ User Code 2
+# Place your code here
+#}}}}
+
+add_library(rdp ${RC_SRCS} ${CXX_SRCS} ${C_SRCS})
+
+
+
+#{{{{ User Code 3
+# Place your code here
+#}}}}
+
diff --git a/src/rdp/include/.clang-format b/src/rdp/include/.clang-format
new file mode 120000
index 0000000..3260daf
--- /dev/null
+++ b/src/rdp/include/.clang-format
@@ -0,0 +1 @@
+../../../.clang-format \ No newline at end of file
diff --git a/src/rdp/include/rdp_backend_api.h b/src/rdp/include/rdp_backend_api.h
new file mode 100644
index 0000000..4840765
--- /dev/null
+++ b/src/rdp/include/rdp_backend_api.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+typedef enum
+{
+ rdp_conn_state_offline,
+ rdp_conn_state_connected
+} rdp_connection_state;
+
+bool rdp_create(wrdp_core_exports *core, wrdp_backend_module *module);
+
+void rdp_backend_destroy(void *backend);
diff --git a/src/rdp/rdp.project b/src/rdp/rdp.project
new file mode 100644
index 0000000..30925d4
--- /dev/null
+++ b/src/rdp/rdp.project
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CodeLite_Project Name="rdp" Version="11000" InternalType="Library">
+ <Plugins>
+ <Plugin Name="qmake">
+ <![CDATA[00010001N0005Debug000000000000]]>
+ </Plugin>
+ </Plugins>
+ <Description/>
+ <Dependencies/>
+ <VirtualDirectory Name="src">
+ <File Name="rdp_rail.h"/>
+ <File Name="rdp_rail.c"/>
+ <File Name="rdp_ft.h"/>
+ <File Name="rdp_ft.c"/>
+ <File Name="rdp_display_output.h"/>
+ <File Name="rdp_display_output.c"/>
+ <File Name="rdp_user_input.h"/>
+ <File Name="rdp_user_input.c"/>
+ <File Name="rdp_channels.h"/>
+ <File Name="rdp_channels.c"/>
+ <File Name="rdp_clipboard.h"/>
+ <File Name="rdp_clipboard.c"/>
+ <File Name="rdp_settings.h"/>
+ <File Name="rdp_settings.c"/>
+ <File Name="rdp_png.c"/>
+ <File Name="rdp_png.h"/>
+ <File Name="rdp_impl.c"/>
+ <File Name="rdp_impl.h"/>
+ <File Name="rdp_module.c"/>
+ <File Name="rdp_module.h"/>
+ </VirtualDirectory>
+ <VirtualDirectory Name="include">
+ <File Name="include/rdp_backend_api.h"/>
+ </VirtualDirectory>
+ <Settings Type="Static Library">
+ <GlobalSettings>
+ <Compiler Options="-fPIC" C_Options="-D_XOPEN_SOURCE=500;-D_POSIX_C_SOURCE=200112L" Assembler="">
+ <IncludePath Value="."/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/include"/>
+ <IncludePath Value="../../3rdparty/libev"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/winpr"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/include"/>
+ <IncludePath Value="../core/include"/>
+ <IncludePath Value="/home/sss/.nix-profile/include"/>
+ </Compiler>
+ <Linker Options="">
+ <LibraryPath Value="."/>
+ </Linker>
+ <ResourceCompiler Options=""/>
+ </GlobalSettings>
+ <Configuration Name="Debug" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Static Library" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-g;-Wall;-fsanitize=address;-O0" C_Options="-pg;-g;-Wall;-DDEBUG;-fsanitize=address;-O0" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0"/>
+ <Linker Options="-pg" Required="yes"/>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName).a" IntermediateDirectory="./Debug" Command="" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="yes">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName/>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ <Configuration Name="Release" CompilerType="clang" DebuggerType="GNU gdb debugger" Type="Static Library" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
+ <Compiler Options="-Wall;-fPIC" C_Options="-std=c99;-Wall;-D_XOPEN_SOURCE=500;-D_POSIX_C_SOURCE=200112L" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0">
+ <IncludePath Value="."/>
+ <IncludePath Value="./include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/winpr/include"/>
+ <IncludePath Value="../../3rdparty/FreeRDP/build/include"/>
+ <IncludePath Value="../core/include"/>
+ <Preprocessor Value="NDEBUG"/>
+ </Compiler>
+ <Linker Options="-O2" Required="yes"/>
+ <ResourceCompiler Options="" Required="no"/>
+ <General OutputFile="$(IntermediateDirectory)/$(ProjectName).a" IntermediateDirectory="./Release" Command="" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(IntermediateDirectory)" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
+ <BuildSystem Name="Default"/>
+ <Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
+ <![CDATA[]]>
+ </Environment>
+ <Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="yes">
+ <DebuggerSearchPaths/>
+ <PostConnectCommands/>
+ <StartupCommands/>
+ </Debugger>
+ <PreBuild/>
+ <PostBuild/>
+ <CustomBuild Enabled="no">
+ <RebuildCommand/>
+ <CleanCommand/>
+ <BuildCommand/>
+ <PreprocessFileCommand/>
+ <SingleFileCommand/>
+ <MakefileGenerationCommand/>
+ <ThirdPartyToolName/>
+ <WorkingDirectory/>
+ </CustomBuild>
+ <AdditionalRules>
+ <CustomPostBuild/>
+ <CustomPreBuild/>
+ </AdditionalRules>
+ <Completion EnableCpp11="no" EnableCpp14="no">
+ <ClangCmpFlagsC/>
+ <ClangCmpFlags/>
+ <ClangPP/>
+ <SearchPaths/>
+ </Completion>
+ </Configuration>
+ </Settings>
+</CodeLite_Project>
diff --git a/src/rdp/rdp_channels.c b/src/rdp/rdp_channels.c
new file mode 100644
index 0000000..a4d95eb
--- /dev/null
+++ b/src/rdp/rdp_channels.c
@@ -0,0 +1,39 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <string.h>
+#include "rdp_channels.h"
+#include "rdp_clipboard.h"
+#include "rdp_rail.h"
+
+void
+rdp_on_channel_connected(void *context, const ChannelConnectedEventArgs *e)
+{
+ if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_cliprdr_init(
+ context, (CliprdrClientContext *)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_rail_init(context, (RailClientContext *)e->pInterface);
+ }
+}
+
+void
+rdp_on_channel_disconnected(
+ void *context, const ChannelDisconnectedEventArgs *e)
+{
+ if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_cliprdr_uninit(
+ context, (CliprdrClientContext *)e->pInterface);
+ }
+ else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0)
+ {
+ rdp_rail_uninit(context, (RailClientContext *)e->pInterface);
+ }
+}
diff --git a/src/rdp/rdp_channels.h b/src/rdp/rdp_channels.h
new file mode 100644
index 0000000..2d895fb
--- /dev/null
+++ b/src/rdp/rdp_channels.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <freerdp/client/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/rail.h>
+
+void rdp_on_channel_connected(
+ void *context, const ChannelConnectedEventArgs *e);
+
+void rdp_on_channel_disconnected(
+ void *context, const ChannelDisconnectedEventArgs *e);
diff --git a/src/rdp/rdp_clipboard.c b/src/rdp/rdp_clipboard.c
new file mode 100644
index 0000000..e8ab882
--- /dev/null
+++ b/src/rdp/rdp_clipboard.c
@@ -0,0 +1,1524 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+/* this code mostly ported from XFreeRDP */
+
+#include <arpa/inet.h>
+#include <iconv.h>
+
+#include "rdp_clipboard.h"
+#include "rdp_ft.h"
+
+static UINT
+rdp_clip_ServerCapabilities(
+ CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
+{
+ UINT32 i;
+ const CLIPRDR_CAPABILITY_SET *caps;
+ const CLIPRDR_GENERAL_CAPABILITY_SET *generalCaps;
+ const BYTE *capsPtr = (const BYTE *)capabilities->capabilitySets;
+ my_rdp_clipboard *clipboard = (my_rdp_clipboard *)context->custom;
+ clipboard->streams_supported = false;
+
+ for (i = 0; i < capabilities->cCapabilitiesSets; i++)
+ {
+ caps = (const CLIPRDR_CAPABILITY_SET *)capsPtr;
+
+ if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
+ {
+ generalCaps
+ = (const CLIPRDR_GENERAL_CAPABILITY_SET *)caps;
+
+ if (generalCaps->generalFlags
+ & CB_STREAM_FILECLIP_ENABLED)
+ {
+ clipboard->streams_supported = true;
+ }
+ }
+
+ capsPtr += caps->capabilitySetLength;
+ }
+ {
+ const char *msg
+ = "rdp_module: cliprdr: server capabilities received";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientCapabilities(CliprdrClientContext* context,
+ CLIPRDR_CAPABILITIES* capabilities)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_cliprdr_send_client_capabilities(my_rdp_clipboard *clipboard)
+{
+ CLIPRDR_CAPABILITIES capabilities;
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets
+ = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet);
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = 12;
+ generalCapabilitySet.version = CB_CAPS_VERSION_2;
+ generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
+ /*|CB_CAN_LOCK_CLIPDATA; */
+
+ if (clipboard->streams_supported)
+ {
+ generalCapabilitySet.generalFlags |= CB_STREAM_FILECLIP_ENABLED
+ | CB_FILECLIP_NO_FILE_PATHS
+ | 0x00000020;
+ }
+
+ {
+ const char *msg
+ = "rdp_module: cliprdr: client capabilities sent";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ return clipboard->clip_context->ClientCapabilities(
+ clipboard->clip_context, &capabilities);
+}
+
+static UINT
+rdp_cliprdr_send_client_format_list(my_rdp_clipboard *clipboard)
+{
+ const uint8_t format_count = 2;
+ CLIPRDR_FORMAT formats[format_count];
+ CLIPRDR_FORMAT_LIST formatList;
+ memset(formats, 0, sizeof(CLIPRDR_FORMAT) * format_count);
+ formats[0].formatId = CF_RAW;
+ formats[1].formatId = CF_TEXT;
+ formatList.common.msgFlags = CB_RESPONSE_OK;
+ formatList.numFormats = format_count;
+ formatList.formats = formats;
+
+ {
+ const char *msg
+ = "rdp_module: cliprdr: client format list sent";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ return clipboard->clip_context->ClientFormatList(
+ clipboard->clip_context, &formatList);
+}
+
+static UINT
+rdp_clip_MonitorReady(
+ CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
+{
+ my_rdp_clipboard *clipboard = context->custom;
+ UINT ret;
+
+ if ((ret = rdp_cliprdr_send_client_capabilities(clipboard))
+ != CHANNEL_RC_OK)
+ return ret;
+
+ if ((ret = rdp_cliprdr_send_client_format_list(clipboard))
+ != CHANNEL_RC_OK)
+ return ret;
+
+ /* clipboard->sync = true; */
+ {
+ const char *msg
+ = "rdp_module: cliprdr: monitor ready message handled";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_TempDirectory(CliprdrClientContext* context,
+ CLIPRDR_TEMP_DIRECTORY* tempDirectory)
+{
+ return CHANNEL_RC_OK;
+} */
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatList(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_LIST* formatList)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_cliprdr_send_client_format_list_response(
+ my_rdp_clipboard *clipboard, BOOL status)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse;
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags
+ = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ formatListResponse.common.dataLen = 0;
+ return clipboard->clip_context->ClientFormatListResponse(
+ clipboard->clip_context, &formatListResponse);
+}
+
+static wrdp_enum_clip_format
+clip_format_from_string(const char *fmt)
+{
+ if (!strcmp(fmt, "FileGroupDescriptorW"))
+ {
+ return clip_format_file_list;
+ }
+ /*
+ * unused
+ else if (!strcmp(fmt, "FileContents"))
+ {
+ }
+ else if (!strcmp(fmt, "Preffered DropEffect"))
+ {
+ }
+ */
+ return clip_format_unsupported;
+}
+
+static wrdp_enum_clip_format
+clip_format_from_id(UINT32 id, my_rdp_clipboard *c)
+{
+ if (id == CF_TEXT || id == CF_OEMTEXT)
+ {
+ return clip_format_text;
+ }
+ else if (id == CF_RAW)
+ {
+ return clip_format_raw;
+ }
+ else if (id == CF_UNICODETEXT)
+ {
+ return clip_format_unicode;
+ }
+ else if (id == CB_FORMAT_TEXTURILIST)
+ {
+ return clip_format_file_list;
+ }
+ return clip_format_unsupported;
+}
+
+static UINT
+rdp_clip_ServerFormatList(
+ CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
+{
+ UINT32 i;
+ uint8_t num_supported_fmts = 0, *out_fmts = 0, num_server_formats;
+ my_rdp_clipboard *clipboard = context->custom;
+ my_clip_format *server_formats = 0;
+ UINT ret;
+
+ if (clipboard->my_rdp_context->ft_to_server->is_runing)
+ {
+ const char *msg
+ = "rdp_module: clipboard: new clipboard formats list "
+ "during runing filetransfer";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ /* return CHANNEL_RC_OK; */
+ }
+
+ num_server_formats = formatList->numFormats + 1; /* +1 for CF_RAW */
+
+ if (!(server_formats
+ = calloc(num_server_formats, sizeof(my_clip_format))))
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "failed to allocate %d my_clip_format structs",
+ num_server_formats);
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_error, 0);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ if (clipboard->srv_fmts && clipboard->srv_fmts_count)
+ {
+ for (i = 0; i < clipboard->srv_fmts_count; ++i)
+ {
+ if (clipboard->srv_fmts[i].rdp_fmt.formatName)
+ {
+ free(clipboard->srv_fmts[i].rdp_fmt.formatName);
+ }
+ }
+ free(clipboard->srv_fmts);
+ }
+
+ clipboard->srv_fmts_count = formatList->numFormats;
+ for (i = 0; i < formatList->numFormats; i++)
+ {
+ CLIPRDR_FORMAT *format = &formatList->formats[i];
+ server_formats[i].rdp_fmt.formatId = format->formatId;
+ wrdp_enum_clip_format fmt
+ = clip_format_from_id(format->formatId, clipboard);
+ if (format->formatName)
+ {
+ server_formats[i].rdp_fmt.formatName
+ = strdup(format->formatName);
+ }
+ if (fmt == clip_format_unsupported && format->formatName)
+ {
+ fmt = clip_format_from_string(format->formatName);
+ }
+ if (fmt != clip_format_unsupported)
+ {
+ num_supported_fmts++;
+ }
+ server_formats[i].my_fmt = fmt;
+ }
+ /* CF_RAW is always implicitly supported by the server */
+ {
+ my_clip_format *format
+ = &server_formats[formatList->numFormats];
+ format->rdp_fmt.formatId = CF_RAW;
+ format->rdp_fmt.formatName = NULL;
+ format->my_fmt = clip_format_raw;
+ num_supported_fmts++;
+ }
+ {
+ out_fmts = calloc(num_supported_fmts, sizeof(uint8_t));
+ if (!out_fmts)
+ {
+ perror("calloc");
+ if (server_formats)
+ {
+ free(server_formats);
+ }
+ return CHANNEL_RC_NO_MEMORY;
+ }
+ uint8_t pos = 0;
+ for (i = 0; i < num_supported_fmts; ++i)
+ {
+ if (server_formats[i].my_fmt == clip_format_unsupported)
+ {
+ continue;
+ }
+ out_fmts[pos] = server_formats[i].my_fmt;
+ pos++;
+ }
+ clipboard->my_rdp_context->my_internals->core->api_clipboard
+ ->clipboard_changed(out_fmts, num_supported_fmts,
+ clipboard->my_rdp_context->my_internals->task_info);
+ free(out_fmts);
+ clipboard->srv_fmts = server_formats;
+ }
+
+ {
+ const char *msg = "rdp_module: cliprdr: server format list"
+ " changed message received";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+
+ ret = rdp_cliprdr_send_client_format_list_response(clipboard, TRUE);
+ return ret;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatListResponse(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerFormatListResponse(CliprdrClientContext *context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
+{
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientLockClipboardData(CliprdrClientContext* context,
+ CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerLockClipboardData(CliprdrClientContext *context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData)
+{
+ /* my_rdp_clipboard* clipboard = context->custom;
+ clipboard->my_rdp_context->ft_to_server->clip_data_id =
+ lockClipboardData->clipDataId; */
+ return CHANNEL_RC_OK;
+}
+
+/*
+ * unused
+static UINT
+rdp_clip_ClientUnlockClipboardData(CliprdrClientContext* context,
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ return CHANNEL_RC_OK;
+}
+*/
+
+static UINT
+rdp_clip_ServerUnlockClipboardData(CliprdrClientContext *context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData)
+{
+ /* TODO: */
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatDataRequest(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerFormatDataRequest(CliprdrClientContext *context,
+ const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
+{
+ my_rdp_clipboard *clipboard = context->custom;
+ wrdp_enum_clip_format fmt = clip_format_from_id(
+ formatDataRequest->requestedFormatId, clipboard);
+ clipboard->my_rdp_context->my_internals->core->api_clipboard
+ ->request_data(
+ fmt, clipboard->my_rdp_context->my_internals->task_info);
+
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFormatDataResponse(CliprdrClientContext* context,
+ CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+/*static UINT rdp_cliprdr_send_data_response(my_rdp_clipboard* clipboard,
+ BYTE* data, int size)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE response;
+ ZeroMemory(&response, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE));
+ response.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+ response.dataLen = size;
+ response.requestedFormatData = data;
+ return clipboard->clip_context->ClientFormatDataResponse(
+ clipboard->clip_context, &response);
+} */
+
+static UINT
+rdp_clip_ServerFormatDataResponse(CliprdrClientContext *context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
+{
+ UINT32 size = formatDataResponse->common.dataLen;
+ const BYTE *data = formatDataResponse->requestedFormatData;
+ my_rdp_clipboard *clipboard = context->custom;
+
+ if (!data)
+ {
+ const char *msg
+ = "rdp_module: cliprdr: failed to get clipboard data";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ return CHANNEL_RC_OK;
+ }
+
+ switch (clipboard->cli_req_fmt_id.my_fmt)
+ {
+ case clip_format_file_list:
+ {
+ /* TODO: check this */
+ UINT error = NO_ERROR;
+ FILEDESCRIPTORW *file_array = 0;
+ UINT32 file_count = 0, pos = 0;
+ uint8_t *msg_struct = 0, *msg = 0;
+ wrdp_backend_ft_list_entry *list = 0;
+ size_t msg_size_result = 0;
+ error = cliprdr_parse_file_list(
+ data, size, &file_array, &file_count);
+ if (error)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "failed to deserialize"
+ " CLIPRDR_FILELIST: 0x%08X",
+ error);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_utils->log_msg((const uint8_t *)buf,
+ strlen(buf), wrdp_log_level_error, 0);
+ return CHANNEL_RC_OK;
+ }
+ msg_struct = calloc(
+ file_count, sizeof(wrdp_backend_ft_list_entry));
+ if (!msg_struct)
+ {
+ perror("calloc");
+ if (file_count && file_array)
+ {
+ free(file_array);
+ }
+ return CHANNEL_RC_OK;
+ }
+ msg_size_result
+ = (file_count * sizeof(wrdp_backend_ft_list_entry))
+ + 3;
+ char *filename_array[file_count];
+ {
+ int i = 0;
+ for (; i < file_count; ++i)
+ {
+ filename_array[i] = 0;
+ }
+ }
+ list = (wrdp_backend_ft_list_entry *)msg_struct;
+ for (; file_array && pos < file_count; ++pos)
+ {
+ list[pos].file_id = pos;
+ if (file_array[pos].cFileName[0])
+ {
+ iconv_t utf16_to_utf8
+ = iconv_open("UTF-8", "UTF-16LE");
+ char *ptr_w, *ptr_r,
+ *tmp_buf
+ = calloc(520, sizeof(char));
+ size_t name_len_utf16
+ = 260 * sizeof(WCHAR),
+ name_len_utf8 = 520,
+ name_len_result = 0;
+ if (!tmp_buf)
+ {
+ perror("calloc");
+ iconv_close(utf16_to_utf8);
+ free(msg_struct);
+ if (file_count)
+ {
+ int i = 0;
+ for (; (i < file_count)
+ && filename_array
+ [i];
+ ++i)
+ {
+ free(
+ filename_array
+ [i]);
+ }
+ free(file_array);
+ }
+ return CHANNEL_RC_OK;
+ }
+ ptr_w = tmp_buf;
+ ptr_r
+ = (char *)file_array[pos].cFileName;
+ while (name_len_utf16 && name_len_utf8)
+ {
+ iconv(utf16_to_utf8, &ptr_r,
+ &name_len_utf16, &ptr_w,
+ &name_len_utf8);
+ }
+#ifdef DEBUG
+ if (!name_len_utf8 && name_len_utf16)
+ {
+ log_msg_info i = {0};
+ i.level = wrdp_log_level_trace;
+ i.buf = (const uint8_t
+ *)"rdp_module: "
+ "cliprdr: iconv: not "
+ "sufficient space in "
+ "output buffer";
+ i.task_info
+ = clipboard->my_internals
+ ->task_info;
+ clipboard->my_internals->core
+ ->api_utils->log_msg_ex(&i);
+ }
+#endif
+ iconv(utf16_to_utf8, 0, 0, &ptr_w,
+ &name_len_utf8);
+ name_len_result = strlen(tmp_buf);
+ msg_size_result += name_len_result;
+ list[pos].filename_len
+ = name_len_result;
+ filename_array[pos] = strdup(tmp_buf);
+ iconv_close(utf16_to_utf8);
+ free(tmp_buf);
+ }
+ /* TODO: check/change endianness */
+ memcpy(&(list[pos].file_size),
+ &file_array[pos].nFileSizeLow, 4);
+ memcpy(((uint8_t *)&(list[pos].file_size)) + 4,
+ &file_array[pos].nFileSizeHigh, 4);
+ }
+ msg = malloc(msg_size_result);
+ if (!msg)
+ {
+ perror("malloc");
+ for (pos = 0; pos < file_count; ++pos)
+ {
+ free(filename_array[pos]);
+ }
+ free(msg_struct);
+ return CHANNEL_RC_OK;
+ }
+ memset(msg, 0, msg_size_result);
+ *((uint8_t *)msg) = clip_format_file_list;
+ memcpy(msg + 1, &file_count, 2);
+ {
+ size_t msg_pos = 3;
+ for (pos = 0; pos < file_count; ++pos)
+ {
+ memcpy(msg + msg_pos,
+ &list[pos].filename_len, 2);
+ msg_pos += 2;
+ memcpy(msg + msg_pos,
+ &list[pos].file_id, 4);
+ msg_pos += 4;
+ memcpy(msg + msg_pos,
+ &list[pos].file_size, 8);
+ msg_pos += 8;
+ /* TODO: looks like i have missed
+ * something ... "Null pointer passed as
+ * an argument to a 'nonnull' parameter"
+ * just added null ptr check for now */
+ if (filename_array[pos])
+ {
+ memcpy(msg + msg_pos,
+ filename_array[pos],
+ list[pos].filename_len);
+ free(filename_array[pos]);
+ }
+ msg_pos += list[pos].filename_len;
+ }
+ }
+ free(msg_struct);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_clipboard->send_data((uint8_t *)msg,
+ msg_size_result,
+ clipboard->my_rdp_context->my_internals
+ ->task_info);
+ free(msg);
+ if (file_count && file_array)
+ {
+ free(file_array);
+ }
+ return CHANNEL_RC_OK;
+ }
+ break;
+ case clip_format_raw:
+ case clip_format_text:
+ {
+ size_t msg_len = size + 1;
+ uint8_t *msg = malloc(msg_len);
+ if (!msg)
+ {
+ perror("malloc");
+ return CHANNEL_RC_OK;
+ }
+ *(uint8_t *)msg = clip_format_text;
+ memcpy(msg + 1, data, size);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_clipboard->send_data(msg, msg_len,
+ clipboard->my_rdp_context->my_internals
+ ->task_info);
+ free(msg);
+ }
+ break;
+ case clip_format_unicode:
+ {
+ iconv_t utf16_to_utf8 = iconv_open("UTF-8", "UTF-16LE");
+ size_t in_left = size, out_left = size;
+ uint8_t *tmp_buf = malloc(out_left), *tmp_buf_ptr;
+ uint8_t *msg = 0;
+ if (!tmp_buf)
+ {
+ perror("malloc");
+ return CHANNEL_RC_OK;
+ }
+ tmp_buf_ptr = tmp_buf;
+ /* wcstombs((char*)msg + 1, tmp_buf, text_len); */
+ while (in_left && out_left)
+ {
+ if (iconv(utf16_to_utf8, (char **)&data,
+ &in_left, (char **)&tmp_buf_ptr,
+ &out_left)
+ == (size_t)-1)
+ {
+ log_msg_info mi = {0};
+ mi.level = wrdp_log_level_warning;
+ mi.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: "
+ "failed to encode outgoing "
+ "buffer from utf8 to utf16le";
+ mi.task_info = clipboard->my_internals
+ ->task_info;
+ clipboard->my_internals->core->api_utils
+ ->log_msg_ex(&mi);
+ free(tmp_buf);
+ iconv_close(utf16_to_utf8);
+ return CHANNEL_RC_NO_BUFFER;
+ }
+ }
+#ifdef DEBUG
+ if (!out_left && in_left)
+ {
+ log_msg_info i = {0};
+ i.level = wrdp_log_level_trace;
+ i.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: not "
+ "sufficient space in output buffer";
+ i.task_info
+ = clipboard->my_internals->task_info;
+ clipboard->my_internals->core->api_utils
+ ->log_msg_ex(&i);
+ }
+#endif
+ iconv(utf16_to_utf8, 0, 0, (char **)&tmp_buf_ptr,
+ &out_left);
+ iconv_close(utf16_to_utf8);
+ msg = malloc(size - out_left + 1);
+ if (!msg)
+ {
+ perror("malloc");
+ return CHANNEL_RC_OK;
+ }
+ *(uint8_t *)msg = clip_format_unicode;
+ memcpy(msg + 1, tmp_buf, size - out_left);
+#ifdef DEBUG
+ {
+ log_msg_info i = {0};
+ size_t len = 1024;
+ char msg_buf[len];
+ snprintf(msg_buf, len - 1,
+ "rdp_module: cliprdr: sending encoded from "
+ "utf16le to utf8 text: %s",
+ tmp_buf);
+ i.level = wrdp_log_level_trace;
+ i.buf = (const uint8_t *)msg_buf;
+ i.task_info
+ = clipboard->my_internals->task_info;
+ clipboard->my_internals->core->api_utils
+ ->log_msg_ex(&i);
+ }
+#endif
+ free(tmp_buf);
+ clipboard->my_rdp_context->my_internals->core
+ ->api_clipboard->send_data(msg, size - out_left + 1,
+ clipboard->my_rdp_context->my_internals
+ ->task_info);
+ free(msg);
+ }
+ break;
+ default:
+ {
+ const char *msg = "rdp_module: cliprdr: unsupported"
+ " clipboardformat requested";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ }
+ break;
+ }
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFileContentsRequest(CliprdrClientContext* context,
+ CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ return CHANNEL_RC_OK;
+}*/
+
+static UINT
+rdp_clip_ServerFileContentsRequest(CliprdrClientContext *context,
+ const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
+{
+ my_rdp_clipboard *clipboard = context->custom;
+ if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE)
+ {
+ CLIPRDR_FILE_CONTENTS_RESPONSE resp;
+ if (fileContentsRequest->nPositionLow
+ || fileContentsRequest->nPositionHigh
+ || (fileContentsRequest->cbRequested != 0x00000008))
+ {
+ const char *msg
+ = "rdp_module: ft: ServerFileContentsRequest: "
+ "malformed request";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return CHANNEL_RC_OK;
+ }
+ memset(&resp, 0, sizeof(CLIPRDR_FILE_CONTENTS_RESPONSE));
+ resp.common.msgType = CB_FILECONTENTS_RESPONSE;
+ resp.common.msgFlags = CB_RESPONSE_OK;
+ resp.streamId = fileContentsRequest->streamId;
+ resp.cbRequested = sizeof(uint64_t);
+ resp.requestedData = (BYTE *)&(
+ clipboard
+ ->client_filelist_cache[fileContentsRequest->listIndex]
+ .file_size);
+ clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &resp);
+ }
+ else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE)
+ {
+ wrdp_backend_ft_file_request req;
+ memset(&req, 0, sizeof(wrdp_backend_ft_list_entry));
+ /* TODO: better way to detect runing transfer */
+ clipboard->my_rdp_context->ft_to_server->is_runing = true;
+ req.file_id = fileContentsRequest->listIndex;
+ req.req_size = fileContentsRequest->cbRequested;
+ clipboard->my_rdp_context->ft_to_server->transfer_id
+ = fileContentsRequest->streamId;
+ memcpy(
+ &req.file_offset, &(fileContentsRequest->nPositionLow), 4);
+ memcpy(((uint8_t *)&req.file_offset) + 4,
+ &(fileContentsRequest->nPositionHigh), 4);
+ clipboard->my_rdp_context->my_internals->core->api_filetransfers
+ ->ft_request(&req,
+ clipboard->my_rdp_context->my_internals->task_info);
+ }
+ return CHANNEL_RC_OK;
+}
+
+/* unused for now
+ * static UINT
+rdp_clip_ClientFileContentsResponse(CliprdrClientContext* context,
+ CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse)
+{
+ return CHANNEL_RC_OK;
+} */
+
+static UINT
+rdp_clip_ServerFileContentsResponse(
+ CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_RESPONSE *resp)
+{
+ /* TODO: somehow avoid memory copying here */
+ my_rdp_clipboard *clipboard = context->custom;
+ wrdp_backend_ft_chunk chunk;
+ if (resp->common.msgFlags & CB_RESPONSE_FAIL)
+ {
+ const char *msg = "rdp_module: ft: ServerFileContentsResponse: "
+ "CB_RESPONSE_FAIL";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto transfer_failed;
+ }
+ if (!resp->cbRequested
+ && (clipboard->my_rdp_context->ft_to_client->transferred
+ != clipboard->my_rdp_context->ft_to_client->file_size))
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "rdp_module: ft: chunk with zero size received on"
+ " position %lu of %lu",
+ clipboard->my_rdp_context->ft_to_client->transferred,
+ clipboard->my_rdp_context->ft_to_client->file_size);
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto transfer_failed;
+ }
+ else if (clipboard->my_rdp_context->ft_to_client->transfer_id
+ != resp->streamId)
+ {
+ const char *msg
+ = "rdp_module: clipboard: ft: transfer_id mismatch in "
+ "ServerFileContentsResponse";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ goto transfer_failed;
+ }
+ else if (resp->cbRequested)
+ {
+ chunk.transfer_id = resp->streamId;
+ chunk.size = resp->cbRequested;
+ clipboard->my_rdp_context->my_internals->core->api_filetransfers
+ ->ft_send_chunk(&chunk, (uint8_t *)(resp->requestedData),
+ clipboard->my_rdp_context->my_internals->task_info);
+ clipboard->my_rdp_context->ft_to_client->transferred
+ += resp->cbRequested;
+ }
+ /* request next file chunk */
+ if (clipboard->my_rdp_context->ft_to_client->transferred
+ < clipboard->my_rdp_context->ft_to_client->file_size)
+ {
+ my_rdp_context *c = clipboard->my_rdp_context;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_req;
+ uint32_t chunk_len = 512000;
+ uint64_t left = 0;
+ memset(&file_req, 0, sizeof(CLIPRDR_FILE_CONTENTS_REQUEST));
+
+ left
+ = c->ft_to_client->file_size - c->ft_to_client->transferred;
+ if (left < chunk_len)
+ {
+ chunk_len = left;
+ }
+ file_req.cbRequested = chunk_len;
+ c->ft_to_client->transfer_id = rand();
+ file_req.streamId = c->ft_to_client->transfer_id;
+ file_req.listIndex = c->ft_to_client->file_id;
+ file_req.dwFlags = FILECONTENTS_RANGE;
+
+ /* TODO: check/change endianness */
+ memcpy(&(file_req.nPositionLow),
+ &(c->ft_to_client->transferred), 4);
+ memcpy(&(file_req.nPositionHigh),
+ (((uint8_t *)&(c->ft_to_client->transferred)) + 4), 4);
+ c->clipboard->clip_context->ClientFileContentsRequest(
+ c->clipboard->clip_context, &file_req);
+ }
+ else if (clipboard->my_rdp_context->ft_to_client->is_runing == true)
+ {
+ my_rdp_context *c = clipboard->my_rdp_context;
+ wrdp_backend_ft_status status;
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "rdp_module: ft: status: transfer finished,"
+ " transfered size: %lu",
+ clipboard->my_rdp_context->ft_to_client
+ ->transferred);
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_trace, 0);
+ }
+ memset(&status, 0, sizeof(wrdp_backend_ft_status));
+ status.status = ft_status_success;
+ status.file_id = c->ft_to_client->file_id;
+ status.transfer_id = c->ft_to_client->transfer_id;
+ c->my_internals->core->api_filetransfers->ft_finish(
+ &status, c->my_internals->task_info);
+ clipboard->my_rdp_context->ft_to_client->file_size = 0;
+ clipboard->my_rdp_context->ft_to_client->transfer_id = 0;
+ clipboard->my_rdp_context->ft_to_client->file_id = 0;
+ clipboard->my_rdp_context->ft_to_client->is_runing = false;
+ clipboard->my_rdp_context->ft_to_client->transferred = 0;
+ }
+ else
+ {
+ const char *msg = "rdp_module: clipboard: ft:"
+ " unexpected data chunk on finished transfer";
+ clipboard->my_rdp_context->my_internals->core->api_utils
+ ->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ }
+ return CHANNEL_RC_OK;
+transfer_failed:
+{
+ my_rdp_context *c = clipboard->my_rdp_context;
+ wrdp_backend_ft_status status;
+ memset(&status, 0, sizeof(wrdp_backend_ft_status));
+ status.status = ft_status_failure;
+ status.file_id = c->ft_to_client->file_id;
+ status.transfer_id = c->ft_to_client->transfer_id;
+ c->my_internals->core->api_filetransfers->ft_finish(
+ &status, c->my_internals->task_info);
+ clipboard->my_rdp_context->ft_to_client->file_size = 0;
+ clipboard->my_rdp_context->ft_to_client->transfer_id = 0;
+ clipboard->my_rdp_context->ft_to_client->file_id = 0;
+ clipboard->my_rdp_context->ft_to_client->is_runing = false;
+ clipboard->my_rdp_context->ft_to_client->transferred = 0;
+}
+ return CHANNEL_RC_OK;
+}
+
+void
+rdp_cliprdr_init(my_rdp_context *ctx, CliprdrClientContext *cliprdr)
+{
+ ctx->clipboard->clip_context = cliprdr;
+ cliprdr->custom = (void *)ctx->clipboard;
+ ctx->clipboard->my_internals = ctx->my_internals;
+ cliprdr->MonitorReady = rdp_clip_MonitorReady;
+ cliprdr->ServerCapabilities = rdp_clip_ServerCapabilities;
+ cliprdr->ServerFormatList = rdp_clip_ServerFormatList;
+ cliprdr->ServerFormatListResponse = rdp_clip_ServerFormatListResponse;
+ cliprdr->ServerFormatDataRequest = rdp_clip_ServerFormatDataRequest;
+ cliprdr->ServerFormatDataResponse = rdp_clip_ServerFormatDataResponse;
+ cliprdr->ServerFileContentsRequest = rdp_clip_ServerFileContentsRequest;
+ cliprdr->ServerFileContentsResponse
+ = rdp_clip_ServerFileContentsResponse;
+ cliprdr->ServerLockClipboardData = rdp_clip_ServerLockClipboardData;
+ cliprdr->ServerUnlockClipboardData = rdp_clip_ServerUnlockClipboardData;
+}
+
+void
+rdp_cliprdr_uninit(my_rdp_context *ctx, CliprdrClientContext *cliprdr)
+{
+ /* do not need to do it here, done during backend destruction */
+ /* cliprdr->custom = NULL;
+
+ if (ctx->clipboard)
+ ctx->clipboard->clip_context = NULL;
+ ctx->clipboard = NULL; */
+}
+
+/* unused for now
+ * static UINT
+ClientRequestFileSize(wClipboardDelegate* delegate,
+ const wClipboardFileSizeRequest* request)
+{
+ return 0;
+}*/
+
+static UINT
+ClipboardFileSizeSuccess(wClipboardDelegate *delegate,
+ const wClipboardFileSizeRequest *request, UINT64 fileSize)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.streamId = request->streamId;
+ response.cbRequested = sizeof(UINT64);
+ response.requestedData = (BYTE *)&fileSize;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+ // return 0;
+}
+static UINT
+ClipboardFileSizeFailure(wClipboardDelegate *delegate,
+ const wClipboardFileSizeRequest *request, UINT errorCode)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.streamId = request->streamId;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+}
+
+/* unused for now
+ * static UINT
+ClientRequestFileRange(wClipboardDelegate* delegate,
+ const wClipboardFileRangeRequest* request)
+{
+ return 0;
+} */
+
+static UINT
+ClipboardFileRangeSuccess(wClipboardDelegate *delegate,
+ const wClipboardFileRangeRequest *request, const BYTE *data, UINT32 size)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.streamId = request->streamId;
+ response.cbRequested = size;
+ response.requestedData = (BYTE *)data;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+}
+
+static UINT
+ClipboardFileRangeFailure(wClipboardDelegate *delegate,
+ const wClipboardFileRangeRequest *request, UINT errorCode)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response;
+ my_rdp_clipboard *clipboard = delegate->custom;
+ ZeroMemory(&response, sizeof(response));
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.streamId = request->streamId;
+ return clipboard->clip_context->ClientFileContentsResponse(
+ clipboard->clip_context, &response);
+}
+
+my_rdp_clipboard *
+rdp_clipboard_new(my_rdp_context *context)
+{
+ my_rdp_clipboard *clip = calloc(1, sizeof(my_rdp_clipboard));
+ if (!clip)
+ {
+ perror("calloc");
+ return 0;
+ }
+ clip->my_rdp_context = context;
+ clip->clipboard = ClipboardCreate();
+ clip->delegate = ClipboardGetDelegate(clip->clipboard);
+ clip->delegate->custom = clip;
+
+ clip->delegate->ClipboardFileSizeSuccess = ClipboardFileSizeSuccess;
+ clip->delegate->ClipboardFileSizeFailure = ClipboardFileSizeFailure;
+ clip->delegate->ClipboardFileRangeSuccess = ClipboardFileRangeSuccess;
+ clip->delegate->ClipboardFileRangeFailure = ClipboardFileRangeFailure;
+
+ context->clipboard = clip;
+
+ return clip;
+}
+
+static UINT
+cliprdr_send_data_request(my_rdp_clipboard *clipboard)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST request;
+ ZeroMemory(&request, sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
+ request.requestedFormatId = clipboard->cli_req_fmt_id.rdp_fmt.formatId;
+ return clipboard->clip_context->ClientFormatDataRequest(
+ clipboard->clip_context, &request);
+}
+
+static bool
+clip_api_handle_request_data(
+ const wrdp_backend_clipbrd_data_request *request, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ i->context->clipboard->cli_req_fmt_id.my_fmt = request->format;
+ switch (request->format)
+ {
+ case clip_format_raw:
+ {
+ /* i->context->clipboard->cli_req_fmt_id = CF_RAW;
+ * raw format does not work for some reason
+ */
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId
+ = CF_TEXT;
+ }
+ break;
+ case clip_format_text:
+ {
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId
+ = CF_TEXT;
+ }
+ break;
+ case clip_format_file_list:
+ {
+ /* 4.5.3 Format Data Request PDU
+ The following is an annotated dump of a Format
+ Data Request PDU (section 2.2.5.1). The format being
+ requested is the File List that was advertised
+ in section 4.5.1 (the advertised ID in the Format
+ List PDU was 49273).
+ */
+ uint8_t p;
+ bool format_found = false;
+ for (p = 0; p < i->context->clipboard->srv_fmts_count;
+ ++p)
+ {
+ if (!strcmp("FileGroupDescriptorW",
+ i->context->clipboard->srv_fmts[p]
+ .rdp_fmt.formatName))
+ {
+ i->context->clipboard->cli_req_fmt_id
+ .rdp_fmt.formatId
+ = i->context->clipboard->srv_fmts[p]
+ .rdp_fmt.formatId;
+ format_found = true;
+ break;
+ }
+ }
+ if (!format_found)
+ {
+ const char *msg = "rdp_module: cliprdr: "
+ "requested data format"
+ " id not found";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt
+ .formatId
+ = CF_RAW;
+ }
+ }
+ break;
+ case clip_format_unicode:
+ {
+ i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId
+ = CF_UNICODETEXT;
+ }
+ break;
+ default:
+ {
+ const char *msg
+ = "rdp_module: cliprdr: unsuported data format"
+ " requested";
+ i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_warning, 0);
+ }
+ break;
+ }
+ cliprdr_send_data_request(i->context->clipboard);
+ return true;
+}
+
+static bool
+clip_api_handle_data_changed(
+ const wrdp_backend_clipbrd_fmts *fmts, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ my_rdp_clipboard *clip = i->context->clipboard;
+ uint8_t pos = 0;
+
+ CLIPRDR_FORMAT *formats = 0;
+ CLIPRDR_FORMAT_LIST formatList;
+
+ if (!fmts->count)
+ {
+ const char *msg = "received empty clipboard formats list";
+ i->core->api_utils->log_msg((const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return false;
+ }
+ formats = calloc(fmts->count, sizeof(CLIPRDR_FORMAT));
+ if (!formats)
+ {
+ perror("calloc");
+ return false;
+ }
+
+ for (; pos < fmts->count; ++pos)
+ {
+ switch ((wrdp_enum_clip_format)fmts->formats[pos])
+ {
+ case clip_format_raw:
+ {
+ formats[pos].formatId = CF_RAW;
+ }
+ break;
+ case clip_format_text:
+ {
+ formats[pos].formatId = CF_TEXT;
+ }
+ break;
+ case clip_format_unicode:
+ {
+ formats[pos].formatId = CF_UNICODETEXT;
+ }
+ break;
+ case clip_format_file_list:
+ {
+ formats[pos].formatId = CB_FORMAT_TEXTURILIST;
+ formats[pos].formatName
+ = "FileGroupDescriptorW";
+ }
+ break;
+ default:
+ {
+ formats[pos].formatId = CF_RAW;
+ }
+ break;
+ }
+ }
+ formatList.common.msgFlags = CB_RESPONSE_OK;
+ formatList.numFormats = fmts->count;
+ formatList.formats = formats;
+ /* TODO: check return value of ClientFormatList */
+ clip->clip_context->ClientFormatList(clip->clip_context, &formatList);
+ free(formats);
+
+ return true;
+}
+
+static bool
+clip_api_handle_send_data(
+ const wrdp_backend_clipbrd_data *data, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ my_rdp_clipboard *clip = i->context->clipboard;
+ CLIPRDR_FORMAT_DATA_RESPONSE response;
+
+ uint8_t type = data->data[0];
+ memset(&response, 0, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE));
+ response.common.msgType = CB_FORMAT_DATA_RESPONSE;
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.common.dataLen = 0;
+ response.requestedFormatData = 0;
+#ifdef DEBUG
+ {
+ const char *msg
+ = "rdp_module: clipboard: from client: raw clipboard"
+ " data:";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ i->core->api_utils->log_msg(data->data, data->size,
+ wrdp_log_level_trace, wrdp_log_flag_binary);
+ }
+#endif
+ switch (type)
+ {
+ case clip_format_raw:
+ case clip_format_text:
+ {
+ if (data->size > 1)
+ {
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.common.dataLen = data->size - 1;
+ response.requestedFormatData = data->data + 1;
+ }
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ return true;
+ }
+ break;
+ case clip_format_unicode:
+ {
+ uint8_t *data_buf = 0;
+ BYTE *msg_buf = 0;
+ iconv_t utf8_to_utf16 = iconv_open("UTF-16LE", "UTF-8");
+ char *ptr_w, *ptr_r;
+ size_t text_len_utf16 = (data->size - 1) * 2,
+ text_len_utf8 = data->size - 1,
+ text_len_result = text_len_utf16;
+ data_buf = calloc(text_len_utf16, sizeof(uint8_t));
+ if (!data_buf)
+ {
+ perror("calloc");
+ return true;
+ }
+ ptr_w = (char *)data_buf;
+ ptr_r = (char *)(data->data + 1);
+ while (text_len_utf8 && text_len_utf16)
+ {
+ if (iconv(utf8_to_utf16, &ptr_r, &text_len_utf8,
+ &ptr_w, &text_len_utf16)
+ == (size_t)-1)
+ {
+ log_msg_info mi = {0};
+ free(data_buf);
+ clip->clip_context
+ ->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ mi.level = wrdp_log_level_warning;
+ mi.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: "
+ "failed to encode outgoing "
+ "buffer from utf8 to utf16le";
+ mi.task_info = i->task_info;
+ i->core->api_utils->log_msg_ex(&mi);
+ iconv_close(utf8_to_utf16);
+ return true;
+ }
+ }
+#ifdef DEBUG
+ if (text_len_utf8 && !text_len_utf16)
+ {
+ log_msg_info mi = {0};
+ mi.level = wrdp_log_level_trace;
+ mi.buf = (const uint8_t
+ *)"rdp_module: cliprdr: iconv: not "
+ "sufficient space in output buffer";
+ mi.task_info = i->task_info;
+ i->core->api_utils->log_msg_ex(&mi);
+ }
+#endif
+ iconv(utf8_to_utf16, 0, 0, &ptr_w, &text_len_utf16);
+ text_len_result = text_len_result - text_len_utf16;
+ iconv_close(utf8_to_utf16);
+ if (text_len_result)
+ {
+ msg_buf = calloc(text_len_result, sizeof(BYTE));
+ memcpy(msg_buf, data_buf, text_len_result);
+ free(data_buf);
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.common.dataLen = text_len_result;
+ response.requestedFormatData = msg_buf;
+ }
+#ifdef DEBUG
+ if (msg_buf)
+ {
+ log_msg_info mi = {0};
+ mi.level = wrdp_log_level_trace;
+ mi.buf = (const uint8_t
+ *)"rdp_module: clipboard: to server:"
+ " utf16 encoded buffer";
+ mi.task_info = i->task_info;
+ i->core->api_utils->log_msg_ex(&mi);
+ mi.flags = wrdp_log_flag_binary;
+ mi.buf = msg_buf;
+ mi.buf_size = text_len_result;
+ i->core->api_utils->log_msg_ex(&mi);
+ }
+#endif
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ if (msg_buf)
+ {
+ free(msg_buf);
+ }
+ return true;
+ }
+ break;
+ case clip_format_file_list:
+ {
+ uint16_t file_count;
+ bool broken_msg = false;
+ int _i;
+ uint64_t data_offset = 3;
+ FILEDESCRIPTORW *file_list = 0;
+ BYTE *format_data = 0;
+ UINT32 format_data_length = 0;
+ memcpy(&file_count, data->data + 1, 2);
+ if (!file_count)
+ {
+ const char *msg = "rdp_module: clipboard: ft: "
+ "empty file_list received";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_warning, 0);
+ return true;
+ }
+ if (clip->client_filelist_cache)
+ {
+ free(clip->client_filelist_cache);
+ }
+ clip->client_filelist_cache
+ = calloc(file_count, sizeof(file_list_cache_entry));
+ if (!clip->client_filelist_cache)
+ {
+ perror("calloc");
+ return false;
+ }
+ file_list = calloc(file_count, sizeof(FILEDESCRIPTOR));
+ if (!file_list)
+ {
+ perror("calloc");
+ return false;
+ }
+ for (_i = 0; _i < file_count; ++_i)
+ {
+ uint16_t filename_len;
+ uint32_t file_id;
+ uint64_t file_size;
+ char *filename, *ptr_w, *ptr_r;
+ iconv_t utf8_to_utf16
+ = iconv_open("UTF-16LE", "UTF-8");
+ size_t text_len_utf16 = 260 * sizeof(WCHAR),
+ text_len_utf8;
+ memcpy(
+ &filename_len, data->data + data_offset, 2);
+ data_offset += 2;
+ if (data_offset >= data->size)
+ {
+ broken_msg = true;
+ break;
+ }
+ text_len_utf8 = filename_len;
+ memcpy(&file_id, data->data + data_offset, 4);
+ clip->client_filelist_cache[_i].file_id
+ = file_id;
+ data_offset += 4;
+ if (data_offset >= data->size)
+ {
+ broken_msg = true;
+ break;
+ }
+ memcpy(&file_size, data->data + data_offset, 8);
+ clip->client_filelist_cache[_i].file_size
+ = file_size;
+ data_offset += 8;
+ if ((data_offset >= data->size)
+ && (_i < file_count))
+ {
+ broken_msg = true;
+ break;
+ }
+ filename = (char *)data->data + data_offset;
+ ptr_r = filename;
+ ptr_w = (char *)(file_list[_i].cFileName);
+ iconv(utf8_to_utf16, &ptr_r, &text_len_utf8,
+ &ptr_w, &text_len_utf16);
+ iconv(utf8_to_utf16, 0, 0, &ptr_w,
+ &text_len_utf16);
+ iconv_close(utf8_to_utf16);
+ data_offset += filename_len;
+ file_list[_i].dwFileAttributes
+ = FILE_ATTRIBUTE_NORMAL;
+ file_list[_i].dwFlags = FD_ATTRIBUTES
+ | FD_FILESIZE
+ | FD_SHOWPROGRESSUI;
+ memcpy(&(file_list[_i].nFileSizeLow),
+ &file_size, 4);
+ memcpy(&(file_list[_i].nFileSizeHigh),
+ ((uint8_t *)&file_size) + 4, 4);
+ }
+ if (broken_msg)
+ {
+ char msg[128];
+ snprintf(msg, 127,
+ "rdp_module: cliprdr: error: wrong file"
+ " list message size: %d, file count: %d\n",
+ data->size, file_count);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ }
+ else
+ {
+ UINT error = NO_ERROR;
+ error = cliprdr_serialize_file_list(file_list,
+ file_count, &format_data,
+ &format_data_length);
+ if (error)
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "failed to serialize "
+ "CLIPRDR_FILELIST:"
+ " 0x%08X",
+ error);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_error, 0);
+ }
+ }
+ free(file_list);
+ if (format_data_length)
+ {
+ response.common.msgFlags = CB_RESPONSE_OK;
+ response.common.dataLen = format_data_length;
+ response.requestedFormatData = format_data;
+ }
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+
+ free(format_data);
+ return true;
+ }
+ break;
+ default:
+ {
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.common.dataLen = 0;
+ response.requestedFormatData = 0;
+ clip->clip_context->ClientFormatDataResponse(
+ clip->clip_context, &response);
+ return true;
+ }
+ break;
+ }
+ /* TODO: check return code from ClientFormatDataResponse */
+ return true;
+}
+
+void
+register_clipboard(wrdp_backend_module *backend)
+{
+ backend->callbacks_clipbrd->request_data = clip_api_handle_request_data;
+ backend->callbacks_clipbrd->send_data = clip_api_handle_send_data;
+ backend->callbacks_clipbrd->data_changed = clip_api_handle_data_changed;
+}
diff --git a/src/rdp/rdp_clipboard.h b/src/rdp/rdp_clipboard.h
new file mode 100644
index 0000000..1536a2c
--- /dev/null
+++ b/src/rdp/rdp_clipboard.h
@@ -0,0 +1,52 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+
+typedef struct
+{
+ CLIPRDR_FORMAT rdp_fmt;
+ wrdp_enum_clip_format my_fmt;
+} my_clip_format;
+
+typedef struct
+{
+ uint32_t file_id;
+ uint64_t file_size;
+} file_list_cache_entry;
+
+struct my_rdp_clipboard_s
+{
+ wClipboard *clipboard;
+ CliprdrClientContext *clip_context;
+ my_rdp_context *my_rdp_context;
+ wClipboardDelegate *delegate;
+ bool streams_supported;
+ uint8_t *data_raw;
+ /* we need to cache server side format list to know dynamic format id */
+ my_clip_format *srv_fmts, cli_req_fmt_id, srv_req_fmt_id;
+ uint8_t srv_fmts_count;
+ file_list_cache_entry *client_filelist_cache;
+
+ rdp_internals *my_internals;
+};
+
+my_rdp_clipboard *rdp_clipboard_new(my_rdp_context *context);
+
+void rdp_cliprdr_init(my_rdp_context *ctx, CliprdrClientContext *cliprdr);
+
+void rdp_cliprdr_uninit(my_rdp_context *ctx, CliprdrClientContext *cliprdr);
+
+void register_clipboard(wrdp_backend_module *backend);
diff --git a/src/rdp/rdp_display_output.c b/src/rdp/rdp_display_output.c
new file mode 100644
index 0000000..16f3c5d
--- /dev/null
+++ b/src/rdp/rdp_display_output.c
@@ -0,0 +1,871 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/file.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/log.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_png.h"
+
+#define RGB_555_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 3 & ~0x7) | (_g >> 2); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define RGB_565_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 2 & ~0x3) | (_g >> 4); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define GetRGB_555(_r, _g, _b, _p) \
+ _r = (_p & 0x7C00) >> 10; \
+ _g = (_p & 0x3E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB_565(_r, _g, _b, _p) \
+ _r = (_p & 0xF800) >> 11; \
+ _g = (_p & 0x7E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB15(_r, _g, _b, _p) \
+ GetRGB_555(_r, _g, _b, _p); \
+ RGB_555_888(_r, _g, _b);
+#define GetRGB16(_r, _g, _b, _p) \
+ GetRGB_565(_r, _g, _b, _p); \
+ RGB_565_888(_r, _g, _b);
+#define RGB24(_r, _g, _b) ((_r << 16) | (_g << 8) | _b)
+#define GetRGB24(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define GetRGB32(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define ARGB32(_a, _r, _g, _b) (_a << 24) | (_r << 16) | (_g << 8) | _b
+#define RGB16(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define RGB15(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define GetARGB32(_a, _r, _g, _b, _p) \
+ _a = (_p & 0xFF000000) >> 24; \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+
+HCLRCONV
+freerdp_clrconv_new(UINT32 flags)
+{
+ HCLRCONV clrconv;
+
+ clrconv = (CLRCONV *)calloc(1, sizeof(CLRCONV));
+
+ if (!clrconv)
+ {
+ perror("calloc");
+ return 0;
+ }
+
+ clrconv->alpha = (flags & CLRCONV_ALPHA) ? TRUE : FALSE;
+ clrconv->invert = (flags & CLRCONV_INVERT) ? TRUE : FALSE;
+ clrconv->rgb555 = (flags & CLRCONV_RGB555) ? TRUE : FALSE;
+
+ clrconv->palette = (rdpPalette *)calloc(1, sizeof(rdpPalette));
+
+ if (!clrconv->palette)
+ {
+ perror("calloc");
+ free(clrconv);
+ return 0;
+ }
+
+ return clrconv;
+}
+
+void
+freerdp_clrconv_free(HCLRCONV clrconv)
+{
+ if (clrconv)
+ {
+ free(clrconv->palette);
+ free(clrconv);
+ }
+}
+
+static int
+freerdp_get_pixel(BYTE *data, int x, int y, int width, int height, int bpp)
+{
+ int start;
+ int shift;
+ UINT16 *src16;
+ UINT32 *src32;
+ int red, green, blue;
+
+ switch (bpp)
+ {
+ case 1:
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ return (data[start] & (0x80 >> shift)) != 0;
+ case 8:
+ return data[y * width + x];
+ case 15:
+ case 16:
+ src16 = (UINT16 *)data;
+ return src16[y * width + x];
+ case 24:
+ data += y * width * 3;
+ data += x * 3;
+ red = data[0];
+ green = data[1];
+ blue = data[2];
+ return RGB24(red, green, blue);
+ case 32:
+ src32 = (UINT32 *)data;
+ return src32[y * width + x];
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void
+freerdp_set_pixel(
+ BYTE *data, int x, int y, int width, int height, int bpp, int pixel)
+{
+ int start;
+ int shift;
+ int *dst32;
+
+ if (bpp == 1)
+ {
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ if (pixel)
+ data[start] = data[start] | (0x80 >> shift);
+ else
+ data[start] = data[start] & ~(0x80 >> shift);
+ }
+ else if (bpp == 32)
+ {
+ dst32 = (int *)data;
+ dst32[y * width + x] = pixel;
+ }
+}
+
+static void
+freerdp_color_split_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ *red = *green = *blue = 0;
+ *alpha = (clrconv->alpha) ? 0xFF : 0x00;
+
+ switch (bpp)
+ {
+ case 32:
+ if (clrconv->alpha)
+ {
+ GetARGB32(*alpha, *red, *green, *blue, *color);
+ }
+ else
+ {
+ GetRGB32(*red, *green, *blue, *color);
+ }
+ break;
+
+ case 24:
+ GetRGB24(*red, *green, *blue, *color);
+ break;
+
+ case 16:
+ GetRGB16(*red, *green, *blue, *color);
+ break;
+
+ case 15:
+ GetRGB15(*red, *green, *blue, *color);
+ break;
+
+ case 8:
+ *color &= 0xFF;
+ *red = clrconv->palette->entries[*color].red;
+ *green = clrconv->palette->entries[*color].green;
+ *blue = clrconv->palette->entries[*color].blue;
+ break;
+
+ case 1:
+ if (*color != 0)
+ {
+ *red = 0xFF;
+ *green = 0xFF;
+ *blue = 0xFF;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+freerdp_color_make_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ switch (bpp)
+ {
+ case 32:
+ *color = ARGB32(*alpha, *red, *green, *blue);
+ break;
+
+ case 24:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 16:
+ if (clrconv->rgb555)
+ {
+ *color = RGB15(*red, *green, *blue);
+ }
+ else
+ {
+ *color = RGB16(*red, *green, *blue);
+ }
+ break;
+
+ case 15:
+ *color = RGB15(*red, *green, *blue);
+ break;
+
+ case 8:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 1:
+ if ((*red != 0) || (*green != 0) || (*blue != 0))
+ *color = 1;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static UINT32
+freerdp_color_convert_rgb(
+ UINT32 srcColor, int srcBpp, int dstBpp, HCLRCONV clrconv)
+{
+ BYTE red = 0;
+ BYTE green = 0;
+ BYTE blue = 0;
+ BYTE alpha = 0xFF;
+ UINT32 dstColor = 0;
+
+ freerdp_color_split_rgb(
+ &srcColor, srcBpp, &red, &green, &blue, &alpha, clrconv);
+ freerdp_color_make_rgb(
+ &dstColor, dstBpp, &red, &green, &blue, &alpha, clrconv);
+
+ return dstColor;
+}
+
+static void
+freerdp_alpha_cursor_convert(BYTE *alphaData, BYTE *xorMask, BYTE *andMask,
+ int width, int height, int bpp, HCLRCONV clrconv)
+{
+ UINT32 xorPixel;
+ UINT32 andPixel;
+ UINT32 x, y, jj;
+
+ for (y = 0; y < height; y++)
+ {
+ jj = (bpp == 1) ? y : (height - 1) - y;
+
+ for (x = 0; x < width; x++)
+ {
+ xorPixel = freerdp_get_pixel(
+ xorMask, x, jj, width, height, bpp);
+ xorPixel = freerdp_color_convert_rgb(
+ xorPixel, bpp, 32, clrconv);
+ andPixel = freerdp_get_pixel(
+ andMask, x, jj, width, height, 1);
+
+ if (andPixel)
+ {
+ if ((xorPixel & 0xFFFFFF) == 0xFFFFFF)
+ {
+ /* use pattern (not solid black) for xor
+ * area */
+ xorPixel = (x & 1) == (y & 1);
+ xorPixel = xorPixel ? 0xFFFFFF : 0;
+ xorPixel |= 0xFF000000;
+ }
+ else if (xorPixel == 0xFF000000)
+ {
+ xorPixel = 0;
+ }
+ }
+
+ freerdp_set_pixel(
+ alphaData, x, y, width, height, 32, xorPixel);
+ }
+ }
+}
+
+static void
+freerdp_bitmap_flip(BYTE *src, BYTE *dst, int scanLineSz, int height)
+{
+ int i;
+
+ BYTE *bottomLine = dst + (scanLineSz * (height - 1));
+ BYTE *topLine = src;
+
+ /* Special processing if called for flip-in-place. */
+ if (src == dst)
+ {
+ /* Allocate a scanline buffer.
+ * (FIXME: malloc / xfree below should be replaced by "get/put
+ * scanline buffer from a pool/Q of fixed buffers" to reuse
+ * fixed size buffers (of max scanline size (or adaptative?) )
+ * -- would be much faster).
+ */
+ BYTE *tmpBfr = (BYTE *)winpr_aligned_malloc(scanLineSz, 16);
+ if (!tmpBfr)
+ {
+ return;
+ }
+ int half = height / 2;
+ /* Flip buffer in place by line permutations through the temp
+ * scan line buffer.
+ * Note that if height has an odd number of line, we don't need
+ * to move the center scanline anyway.
+ * Also note that in place flipping takes three memcpy() calls
+ * to process two scanlines while src to distinct dest would
+ * only requires two memcpy() calls for two scanlines.
+ */
+ height--;
+ for (i = 0; i < half; i++)
+ {
+ CopyMemory(tmpBfr, topLine, scanLineSz);
+ CopyMemory(topLine, bottomLine, scanLineSz);
+ CopyMemory(bottomLine, tmpBfr, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ height--;
+ }
+ winpr_aligned_free(tmpBfr);
+ }
+ /* Flip from source buffer to destination buffer. */
+ else
+ {
+ for (i = 0; i < height; i++)
+ {
+ if (bottomLine && topLine && scanLineSz)
+ {
+ CopyMemory(bottomLine, topLine, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ }
+ }
+ }
+}
+
+static BYTE *
+freerdp_image_flip(BYTE *srcData, BYTE *dstData, int width, int height, int bpp)
+{
+ int scanline;
+
+ scanline = width * ((bpp + 7) / 8);
+
+ if (!dstData)
+ dstData = (BYTE *)winpr_aligned_malloc(
+ width * height * ((bpp + 7) / 8), 16);
+
+ if (!dstData)
+ {
+ return NULL;
+ }
+
+ freerdp_bitmap_flip(srcData, dstData, scanline, height);
+
+ return dstData;
+}
+
+/* outgoing rdp protocol to client implementation */
+
+static BOOL
+Pointer_New(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ HCLRCONV hclrconv = c->my_internals->clrconv;
+ size_t psize = pointer->width * pointer->height * 4;
+
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ p->id = c->my_internals->m_ptrId++;
+ uint8_t *pixels = malloc(sizeof(uint8_t) * psize);
+ if (!pixels)
+ {
+ perror("malloc");
+ return FALSE;
+ }
+ memset(pixels, 0, psize);
+ if ((pointer->andMaskData != 0) && (pointer->xorMaskData != 0))
+ {
+ //XXX
+ freerdp_alpha_cursor_convert(pixels, pointer->xorMaskData,
+ pointer->andMaskData, pointer->width, pointer->height,
+ pointer->xorBpp, hclrconv);
+ }
+
+ //check if the cursor is fully transparent
+ bool transparent = TRUE;
+ for (int y = 0; y < pointer->height; y++)
+ {
+ for (int x = 0; x < pointer->width; x++)
+ {
+ if (pixels[0 + x * 4 + y * 4 * pointer->width] != 0)
+ {
+ transparent = FALSE;
+ }
+ }
+ }
+ if (transparent)
+ {
+ pixels[3] = 1;
+ }
+
+ rdp_png_buf png_buf;
+ memset(&png_buf, 0, sizeof(png_buf));
+ png_buf.buf_size
+ = (pointer->width * pointer->height * sizeof(uint32_t))
+ + 8 /* allocating fullsize 32but argb pixels + png header */;
+ png_buf.buf = malloc(png_buf.buf_size);
+ if (!png_buf.buf)
+ {
+ free(pixels);
+ perror("malloc");
+ return FALSE;
+ }
+ png_generate_from_argb(
+ pointer->width, pointer->height, pixels, &png_buf);
+
+ wrdp_core_display_cursor cur = {0x00, 0x00, 0x02, 0x00, 0x01, 0x00,
+ (unsigned char)pointer->width, (unsigned char)pointer->height, 0x00,
+ 0x00, (unsigned char)pointer->xPos,
+ (unsigned char)(pointer->xPos >> 8), (unsigned char)pointer->yPos,
+ (unsigned char)(pointer->yPos >> 8), (unsigned char)png_buf.written,
+ (unsigned char)(png_buf.written >> 8),
+ (unsigned char)(png_buf.written >> 16),
+ (unsigned char)(png_buf.written >> 24), 0x16, 0x00, 0x00, 0x00};
+
+ cur.data = png_buf.buf;
+ wrdp_core_display_cursor_info cursor_info
+ = {p->id, pointer->xPos, pointer->yPos, png_buf.written, &cur};
+
+ c->my_internals->core->api_paint->send_ptr_new(
+ &cursor_info, c->my_internals->task_info);
+ free(png_buf.buf);
+ free(pixels);
+ return TRUE;
+}
+
+static void
+Pointer_Free(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (p->id)
+ {
+ c->my_internals->core->api_paint->send_ptr_free(
+ p->id, c->my_internals->task_info);
+ }
+}
+
+static BOOL
+Pointer_Set(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_ptr_set(
+ p->id, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetNull(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_ptr_set_null(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetDefault(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_ptr_set_default(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+void
+register_pointer(rdpPointer *p)
+{
+ p->New = Pointer_New;
+ p->Free = Pointer_Free;
+ p->Set = Pointer_Set;
+ p->SetNull = Pointer_SetNull;
+ p->SetDefault = Pointer_SetDefault;
+}
+
+static BOOL
+BeginPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_begin_paint(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+EndPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->api_paint->send_end_paint(
+ c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+SetBounds(rdpContext *context, const rdpBounds *bounds)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ wrdp_core_display_bounds lB;
+ if (bounds)
+ {
+ lB.left = bounds->left;
+ lB.right = bounds->right + 1;
+ lB.top = bounds->top;
+ lB.bottom = bounds->bottom + 1;
+ }
+ else
+ {
+ memset(&lB, 0, sizeof(wrdp_core_display_bounds));
+ }
+ c->my_internals->core->api_paint->send_set_bounds(
+ &lB, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Synchronize(rdpContext *context)
+{
+ return TRUE;
+}
+
+static BOOL
+DesktopResize(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ char buf[64];
+ snprintf(buf, 63, "R:%dx%d", context->settings->DesktopWidth,
+ context->settings->DesktopHeight);
+ c->my_internals->core->api_msgs->send_text_msg(
+ buf, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+BitmapUpdate(rdpContext *context, const BITMAP_UPDATE *bitmap)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ int i;
+ BITMAP_DATA *bmd;
+ for (i = 0; i < (int)bitmap->number; i++)
+ {
+ bmd = &bitmap->rectangles[i];
+ wrdp_core_display_bmp bmp = {bmd->destLeft, bmd->destTop,
+ bmd->width, bmd->height, bmd->destRight - bmd->destLeft + 1,
+ bmd->destBottom - bmd->destTop + 1, bmd->bitsPerPixel,
+ bmd->compressed, bmd->bitmapLength};
+ if (!bmd->compressed)
+ {
+ freerdp_image_flip(bmd->bitmapDataStream,
+ bmd->bitmapDataStream, bmd->width, bmd->height,
+ bmd->bitsPerPixel);
+ }
+ c->my_internals->core->api_paint->send_bitmap(
+ &bmp, bmd->bitmapDataStream, c->my_internals->task_info);
+ }
+ return TRUE;
+}
+
+static BOOL
+Palette(rdpContext *c, const PALETTE_UPDATE *p)
+{
+ return TRUE;
+}
+
+static BOOL
+PlaySound(rdpContext *c, const PLAY_SOUND_UPDATE *s)
+{
+ return TRUE;
+}
+
+static BOOL
+RefreshRect(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+static BOOL
+SuppressOutput(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceCommand(rdpContext* c, wStream* s)
+{
+ return TRUE;
+}*/
+
+static BOOL
+SurfaceBits(rdpContext *c, const SURFACE_BITS_COMMAND *sbc)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceFrameMarker(rdpContext* c, SURFACE_FRAME_MARKER* sfm)
+{
+ return TRUE;
+} */
+
+void
+RegisterUpdate(freerdp *rdp)
+{
+ rdpUpdate *u = rdp->context->update;
+ u->BeginPaint = BeginPaint;
+ u->EndPaint = EndPaint;
+ u->SetBounds = SetBounds;
+ u->Synchronize = Synchronize;
+ u->DesktopResize = DesktopResize;
+ u->BitmapUpdate = BitmapUpdate;
+ u->Palette = Palette;
+ u->PlaySound = PlaySound;
+ u->SurfaceBits = SurfaceBits;
+
+ u->RefreshRect = RefreshRect;
+ u->SuppressOutput = SuppressOutput;
+}
+
+static BOOL
+DstBlt(rdpContext *c, const DSTBLT_ORDER *_do)
+{
+ return TRUE;
+}
+
+static BOOL
+PatBlt(rdpContext *ctx, PATBLT_ORDER *po)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(po->bRop);
+ if (GDI_BS_SOLID == po->brush.style)
+ {
+ wrdp_core_display_patblt_order patblt
+ = {po->nLeftRect, po->nTopRect, po->nWidth, po->nHeight,
+ FreeRDPConvertColor(po->foreColor, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_ABGR32,
+ NULL), //XXX
+ rop3};
+ c->my_internals->core->api_paint->send_pat_blt(
+ &patblt, c->my_internals->task_info);
+ }
+ return TRUE;
+}
+
+static BOOL
+ScrBlt(rdpContext *ctx, const SCRBLT_ORDER *sbo)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(sbo->bRop);
+ wrdp_core_display_scr_blt scr_blt = {rop3, sbo->nLeftRect,
+ sbo->nTopRect, sbo->nWidth, sbo->nHeight, sbo->nXSrc, sbo->nYSrc};
+ c->my_internals->core->api_paint->send_scr_blt(
+ &scr_blt, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+OpaqueRect(rdpContext *context, const OPAQUE_RECT_ORDER *oro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ wrdp_core_display_opaque_rect_order oro2;
+ oro2.color = oro->color;
+ oro2.nHeight = oro->nHeight;
+ oro2.nLeftRect = oro->nLeftRect;
+ oro2.nTopRect = oro->nTopRect;
+ oro2.nWidth = oro->nWidth;
+ oro2.color = FreeRDPConvertColor(
+ oro2.color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ c->my_internals->core->api_paint->send_opaque_rect_order(
+ &oro2, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+DrawNineGrid(rdpContext *c, const DRAW_NINE_GRID_ORDER *dngo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiDstBlt(rdpContext *c, const MULTI_DSTBLT_ORDER *mdo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiPatBlt(rdpContext *c, const MULTI_PATBLT_ORDER *mpo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiScrBlt(rdpContext *c, const MULTI_SCRBLT_ORDER *mso)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiOpaqueRect(rdpContext *context, const MULTI_OPAQUE_RECT_ORDER *moro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ wrdp_core_display_m_opaque_rect mrect;
+ mrect.color = FreeRDPConvertColor(
+ moro->color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ mrect.num_rect = moro->numRectangles;
+ mrect.rects = (wrdp_core_display_delta_rect *)(moro->rectangles);
+ c->my_internals->core->api_paint->send_multi_opaque_rect(
+ &mrect, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+MultiDrawNineGrid(rdpContext *c, const MULTI_DRAW_NINE_GRID_ORDER *mdngo)
+{
+ return TRUE;
+}
+
+static BOOL
+LineTo(rdpContext *c, const LINE_TO_ORDER *lto)
+{
+ return TRUE;
+}
+
+static BOOL
+Polyline(rdpContext *c, const POLYLINE_ORDER *po)
+{
+ return TRUE;
+}
+
+static BOOL
+MemBlt(rdpContext *c, MEMBLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+Mem3Blt(rdpContext *c, MEM3BLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+SaveBitmap(rdpContext *c, const SAVE_BITMAP_ORDER *sbo)
+{
+ return TRUE;
+}
+
+static BOOL
+GlyphIndex(rdpContext *c, GLYPH_INDEX_ORDER *gio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastIndex(rdpContext *c, const FAST_INDEX_ORDER *fio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastGlyph(rdpContext *c, const FAST_GLYPH_ORDER *fgo)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonSC(rdpContext *c, const POLYGON_SC_ORDER *pso)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonCB(rdpContext *c, POLYGON_CB_ORDER *pco)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseSC(rdpContext *c, const ELLIPSE_SC_ORDER *eso)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseCB(rdpContext *c, const ELLIPSE_CB_ORDER *eco)
+{
+ return TRUE;
+}
+void
+RegisterPrimary(freerdp *rdp)
+{
+ rdpPrimaryUpdate *p = rdp->context->update->primary;
+ p->DstBlt = DstBlt;
+ p->PatBlt = PatBlt;
+ p->ScrBlt = ScrBlt;
+ p->OpaqueRect = OpaqueRect;
+ p->DrawNineGrid = DrawNineGrid;
+ p->MultiDstBlt = MultiDstBlt;
+ p->MultiPatBlt = MultiPatBlt;
+ p->MultiScrBlt = MultiScrBlt;
+ p->MultiOpaqueRect = MultiOpaqueRect;
+ p->MultiDrawNineGrid = MultiDrawNineGrid;
+ p->LineTo = LineTo;
+ p->Polyline = Polyline;
+ p->MemBlt = MemBlt;
+ p->Mem3Blt = Mem3Blt;
+ p->SaveBitmap = SaveBitmap;
+ p->GlyphIndex = GlyphIndex;
+ p->FastIndex = FastIndex;
+ p->FastGlyph = FastGlyph;
+ p->PolygonSC = PolygonSC;
+ p->PolygonCB = PolygonCB;
+ p->EllipseSC = EllipseSC;
+ p->EllipseCB = EllipseCB;
+}
diff --git a/src/rdp/rdp_display_output.h b/src/rdp/rdp_display_output.h
new file mode 100644
index 0000000..14ae7f3
--- /dev/null
+++ b/src/rdp/rdp_display_output.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void RegisterUpdate(freerdp *rdp);
+void RegisterPrimary(freerdp *rdp);
+
+void rdp_register_base(freerdp *r);
+
+HCLRCONV
+freerdp_clrconv_new(UINT32 flags);
+
+void freerdp_clrconv_free(HCLRCONV clrconv);
diff --git a/src/rdp/rdp_ft.c b/src/rdp/rdp_ft.c
new file mode 100644
index 0000000..0c5575e
--- /dev/null
+++ b/src/rdp/rdp_ft.c
@@ -0,0 +1,129 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "webrdp_module_api.h"
+#include "webrdp_core_api.h"
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_ft.h"
+#include "rdp_clipboard.h"
+
+static bool
+ft_api_handle_request(
+ const wrdp_backend_ft_file_request *request, void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_req;
+ uint32_t chunk_len = 512000;
+ size_t left = 0;
+ if (i->context->ft_to_client->is_runing)
+ {
+ const char *msg
+ = "rdp_module: clipboard: filetransfers: transfer "
+ "already runing, cannot start new one";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return true;
+ }
+ memset(&file_req, 0, sizeof(CLIPRDR_FILE_CONTENTS_REQUEST));
+ i->context->ft_to_client->transfer_id = rand();
+ i->context->ft_to_client->file_size = request->req_size;
+ i->context->ft_to_client->file_id = request->file_id;
+
+ i->context->ft_to_client->transferred = request->file_offset;
+ i->context->ft_to_client->is_runing = true;
+
+ left = i->context->ft_to_client->file_size
+ - i->context->ft_to_client->transferred;
+ if (left < chunk_len)
+ {
+ chunk_len = left;
+ }
+ file_req.cbRequested = chunk_len;
+ file_req.listIndex = request->file_id;
+ file_req.streamId = i->context->ft_to_client->transfer_id;
+ file_req.dwFlags = FILECONTENTS_RANGE;
+ /* file_req.haveClipDataId = TRUE;
+ file_req.clipDataId =
+ i->context->ft_to_client->clipboard_data_id; */
+
+ /* TODO: check/change endianness */
+ memcpy(&(file_req.nPositionLow),
+ &(i->context->ft_to_client->transferred), 4);
+ memcpy(&(file_req.nPositionHigh),
+ (((uint8_t *)&(i->context->ft_to_client->transferred)) + 4), 4);
+ i->context->clipboard->clip_context->ClientFileContentsRequest(
+ i->context->clipboard->clip_context, &file_req);
+
+ return true;
+}
+
+static bool
+ft_api_handle_chunk(const wrdp_backend_ft_chunk *chunk, const uint8_t *data,
+ void *backend_internals)
+{
+ rdp_internals *i = backend_internals;
+ CLIPRDR_FILE_CONTENTS_RESPONSE resp;
+ memset(&resp, 0, sizeof(CLIPRDR_FILE_CONTENTS_RESPONSE));
+ resp.cbRequested = chunk->size;
+ resp.streamId = i->context->ft_to_server->transfer_id;
+ resp.common.msgFlags = CB_RESPONSE_OK;
+ resp.common.msgType = CB_FILECONTENTS_RESPONSE;
+ resp.requestedData = data;
+ i->context->clipboard->clip_context->ClientFileContentsResponse(
+ i->context->clipboard->clip_context, &resp);
+ i->context->ft_to_server->transferred += chunk->size;
+ if (i->context->ft_to_server->transferred
+ == i->context->ft_to_server->file_size)
+ {
+ i->context->ft_to_server->is_runing = false;
+ i->context->ft_to_server->transfer_id = 0;
+ i->context->ft_to_server->file_id = 0;
+ i->context->ft_to_server->file_size = 0;
+ i->context->ft_to_server->transferred = 0;
+ }
+ return true;
+}
+
+static bool
+ft_api_handle_finish(
+ const wrdp_backend_ft_status *status, void *backend_internals)
+{
+ /* TODO: handle errors */
+ rdp_internals *i = backend_internals;
+ if (status->status != ft_status_success)
+ {
+ /* TODO: test it */
+ CLIPRDR_FILE_CONTENTS_RESPONSE resp;
+ memset(&resp, 0, sizeof(CLIPRDR_FILE_CONTENTS_RESPONSE));
+ const char *msg = "filetransfer finished with error";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ resp.common.msgType = CB_FILECONTENTS_RESPONSE;
+ resp.common.msgFlags = CB_RESPONSE_FAIL;
+ resp.streamId = i->context->ft_to_server->transfer_id;
+ resp.cbRequested = 0;
+ resp.requestedData = 0;
+ i->context->clipboard->clip_context->ClientFileContentsResponse(
+ i->context->clipboard->clip_context, &resp);
+ }
+ i->context->ft_to_server->is_runing = false;
+ i->context->ft_to_server->transfer_id = 0;
+ i->context->ft_to_server->file_size
+ = i->context->ft_to_server->transferred = 0;
+ return true;
+}
+
+void
+register_ft(wrdp_backend_module *backend)
+{
+ backend->callbacks_ft->chunk = ft_api_handle_chunk;
+ backend->callbacks_ft->finish = ft_api_handle_finish;
+ backend->callbacks_ft->request = ft_api_handle_request;
+}
diff --git a/src/rdp/rdp_ft.h b/src/rdp/rdp_ft.h
new file mode 100644
index 0000000..38b6a34
--- /dev/null
+++ b/src/rdp/rdp_ft.h
@@ -0,0 +1,17 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+struct my_rdp_ft_s
+{
+ uint32_t transfer_id, file_id;
+ bool is_runing;
+ uint64_t file_size, transferred;
+};
+
+void register_ft(wrdp_backend_module *backend);
+
diff --git a/src/rdp/rdp_impl.c b/src/rdp/rdp_impl.c
new file mode 100644
index 0000000..3aa47e2
--- /dev/null
+++ b/src/rdp/rdp_impl.c
@@ -0,0 +1,637 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <errno.h>
+#include <locale.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <sys/select.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/file.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <freerdp/log.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_display_output.h"
+#include "rdp_channels.h"
+#include "rdp_clipboard.h"
+#include "rdp_ft.h"
+#include "rdp_user_input.h"
+
+/* from winpr */
+
+typedef BOOL (*pcIsHandled)(HANDLE handle);
+typedef BOOL (*pcCloseHandle)(HANDLE handle);
+typedef int (*pcGetFd)(HANDLE handle);
+typedef DWORD (*pcCleanupHandle)(HANDLE handle);
+typedef BOOL (*pcReadFile)(PVOID Object, LPVOID lpBuffer,
+ DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcReadFileEx)(HANDLE hFile, LPVOID lpBuffer,
+ DWORD nNumberOfBytesToRead, LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+typedef BOOL (*pcReadFileScatter)(HANDLE hFile,
+ FILE_SEGMENT_ELEMENT aSegmentArray[], DWORD nNumberOfBytesToRead,
+ LPDWORD lpReserved, LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcWriteFile)(PVOID Object, LPCVOID lpBuffer,
+ DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcWriteFileEx)(HANDLE hFile, LPCVOID lpBuffer,
+ DWORD nNumberOfBytesToWrite, LPOVERLAPPED lpOverlapped,
+ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+typedef BOOL (*pcWriteFileGather)(HANDLE hFile,
+ FILE_SEGMENT_ELEMENT aSegmentArray[], DWORD nNumberOfBytesToWrite,
+ LPDWORD lpReserved, LPOVERLAPPED lpOverlapped);
+typedef DWORD (*pcGetFileSize)(HANDLE handle, LPDWORD lpFileSizeHigh);
+typedef BOOL (*pcFlushFileBuffers)(HANDLE hFile);
+typedef BOOL (*pcSetEndOfFile)(HANDLE handle);
+typedef DWORD (*pcSetFilePointer)(HANDLE handle, LONG lDistanceToMove,
+ PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);
+typedef BOOL (*pcSetFilePointerEx)(HANDLE hFile, LARGE_INTEGER liDistanceToMove,
+ PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod);
+typedef BOOL (*pcLockFile)(HANDLE hFile, DWORD dwFileOffsetLow,
+ DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow,
+ DWORD nNumberOfBytesToLockHigh);
+typedef BOOL (*pcLockFileEx)(HANDLE hFile, DWORD dwFlags, DWORD dwReserved,
+ DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcUnlockFile)(HANDLE hFile, DWORD dwFileOffsetLow,
+ DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToUnlockLow,
+ DWORD nNumberOfBytesToUnlockHigh);
+typedef BOOL (*pcUnlockFileEx)(HANDLE hFile, DWORD dwReserved,
+ DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh,
+ LPOVERLAPPED lpOverlapped);
+typedef BOOL (*pcSetFileTime)(HANDLE hFile, const FILETIME *lpCreationTime,
+ const FILETIME *lpLastAccessTime, const FILETIME *lpLastWriteTime);
+
+typedef struct _HANDLE_OPS
+{
+ pcIsHandled IsHandled;
+ pcCloseHandle CloseHandle;
+ pcGetFd GetFd;
+ pcCleanupHandle CleanupHandle;
+ pcReadFile ReadFile;
+ pcReadFileEx ReadFileEx;
+ pcReadFileScatter ReadFileScatter;
+ pcWriteFile WriteFile;
+ pcWriteFileEx WriteFileEx;
+ pcWriteFileGather WriteFileGather;
+ pcGetFileSize GetFileSize;
+ pcFlushFileBuffers FlushFileBuffers;
+ pcSetEndOfFile SetEndOfFile;
+ pcSetFilePointer SetFilePointer;
+ pcSetFilePointerEx SetFilePointerEx;
+ pcLockFile LockFile;
+ pcLockFileEx LockFileEx;
+ pcUnlockFile UnlockFile;
+ pcUnlockFileEx UnlockFileEx;
+ pcSetFileTime SetFileTime;
+} HANDLE_OPS;
+
+#define WINPR_HANDLE_DEF() \
+ ULONG Type; \
+ ULONG Mode; \
+ HANDLE_OPS *ops
+
+struct winpr_handle
+{
+ WINPR_HANDLE_DEF();
+};
+typedef struct winpr_handle WINPR_HANDLE;
+
+static INLINE BOOL
+winpr_Handle_GetInfo(HANDLE handle, ULONG *pType, WINPR_HANDLE **pObject)
+{
+ WINPR_HANDLE *wHandle;
+
+ if (handle == NULL || handle == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ wHandle = (WINPR_HANDLE *)handle;
+
+ *pType = wHandle->Type;
+ *pObject = handle;
+
+ return TRUE;
+}
+
+static INLINE int
+winpr_Handle_getFd(HANDLE handle)
+{
+ WINPR_HANDLE *hdl;
+ ULONG type;
+
+ if (!winpr_Handle_GetInfo(handle, &type, &hdl))
+ return -1;
+
+ if (!hdl || !hdl->ops || !hdl->ops->GetFd)
+ return -1;
+
+ return hdl->ops->GetFd(handle);
+}
+
+/* end from winpr */
+
+bool
+rdp_init_internals(void *internals)
+{
+ setlocale(LC_ALL, "");
+#ifdef DEBUG
+ setenv("WLOG_LEVEL", "DEBUG", 1);
+#else
+ setenv("WLOG_LEVEL", "ERROR", 1);
+#endif
+ rdp_internals *i = internals;
+ i->instance = freerdp_new();
+ if (!i->instance)
+ {
+ const char *msg
+ = "rdp_module: Failed to create FreeRDP instance";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ rdp_register_base(i->instance);
+ i->instance->ContextSize = sizeof(my_rdp_context);
+ if (!freerdp_context_new(i->instance))
+ {
+ const char *msg
+ = "rdp_module: Failed to create FreeRDP context";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ i->context = (my_rdp_context *)i->instance->context;
+ ((my_rdp_context *)i->instance->context)->my_internals = internals;
+ {
+ char **argv = malloc(sizeof(char *));
+ if (!argv)
+ {
+ perror("malloc");
+ goto error;
+ }
+ argv[0] = strdup("./core");
+ rdpSettings *settings = i->instance->context->settings;
+ freerdp_client_settings_parse_command_line(
+ settings, 1, argv, FALSE);
+ free(argv[0]);
+ free(argv);
+ if (!settings)
+ {
+ const char *msg
+ = "rdp_module: Failed to create FreeRDP settings";
+ i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error, 0);
+ goto error;
+ }
+ settings->ThreadingFlags = THREADING_FLAGS_DISABLE_THREADS;
+
+ settings->OsMajorType = OSMAJORTYPE_UNIX;
+ settings->OsMinorType = OSMINORTYPE_UNSPECIFIED;
+
+ /* unsupported */
+ settings->RemoteFxCodec = FALSE;
+ settings->NSCodec = FALSE;
+
+ /* turn on clipboard redirect */
+ settings->RedirectClipboard = TRUE;
+ }
+ i->context->ft_to_server = calloc(1, sizeof(my_rdp_ft));
+ if (!i->context->ft_to_server)
+ {
+ perror("calloc");
+ goto error;
+ }
+ i->context->ft_to_client = calloc(1, sizeof(my_rdp_ft));
+ if (!i->context->ft_to_client)
+ {
+ perror("calloc");
+ goto error;
+ }
+ freerdp_register_addin_provider(
+ freerdp_channels_load_static_addin_entry, 0);
+ i->settings = i->instance->context->settings;
+ //i->instance->settings = i->settings;
+ i->settings->instance = i->instance;
+ i->instance->context->settings = i->settings;
+ return true;
+error:
+ if (i->instance)
+ {
+ if (i->instance->context)
+ {
+ freerdp_context_free(i->instance);
+ }
+ freerdp_free(i->instance);
+ i->instance = 0;
+ }
+ if (i->context->ft_to_client)
+ {
+ free(i->context->ft_to_client);
+ }
+ if (i->context->ft_to_server)
+ {
+ free(i->context->ft_to_server);
+ }
+ {
+ const char *msg = "rdp_module: internal error";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+ i->core->api_msgs->send_error_msg(
+ "rdp_module_internal_error", i->task_info);
+ return false;
+}
+
+static void
+rdp_check_all_fds(struct ev_loop *loop, ev_io *w)
+{
+ rdp_internals *i = w->data;
+ bool failure = false;
+ if (i->backend->stopped)
+ return;
+ if ((freerdp_check_event_handles(i->instance->context)) != TRUE)
+ {
+ uint32_t e;
+ e = freerdp_error_info(i->instance);
+ switch (e)
+ {
+ case 1:
+ case 2:
+ case 7:
+ case 9:
+ /* case 12: */
+ /* No really an error
+ * (Happens when you invoke Disconnect
+ * in Start-Menu) */
+ break;
+ case 5:
+ {
+ const char *msg
+ = "rdp_module: Another user connected to "
+ "the server,\nforcing the "
+ "disconnection of the "
+ "current connection.";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_debug, 0);
+ break;
+ }
+ default:
+ {
+ failure = true;
+ }
+ break;
+ }
+ if (failure)
+ {
+ char buf[512];
+ snprintf(buf, 511, "%s",
+ freerdp_get_last_error_name(
+ freerdp_get_last_error(i->instance->context)));
+ i->core->api_msgs->send_error_msg(buf, i->task_info);
+ i->core->api_utils->log_msg((const uint8_t *)buf,
+ strlen(buf), wrdp_log_level_error, 0);
+ goto cleanup;
+ }
+ }
+ if (freerdp_shall_disconnect_context(i->instance->context))
+ {
+ {
+ const char *msg = "rdp_module: disconnect, no error";
+ i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_trace, 0);
+ }
+ freerdp_disconnect(i->instance);
+ i->conn_state = rdp_conn_state_offline;
+ i->core->api_msgs->send_error_msg(
+ "rdp_module_disconnect_no_error", i->task_info);
+ goto cleanup;
+ }
+ return;
+cleanup:
+ ev_io_stop(loop, w);
+ if (!failure)
+ {
+ i->core->api_msgs->send_termination_msg(i->task_info);
+ }
+ i->core->api_core->task_finished(true, i->task_info);
+}
+
+static void
+rdp_fd_readable_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+ rdp_check_all_fds(loop, w);
+}
+
+bool
+rdp_connect(void *internals)
+{
+ rdp_internals *_i = internals;
+#ifdef DEBUG
+ {
+ /* print settings before connect for debug */
+ char buf[512];
+ snprintf(buf, 511,
+ "rdp_module: settings:\n\tfntlm: %ld\n\t"
+ "height: %ld\n\thost: %s\n\tnomani: %ld\n\tnonla: %ld\n\t"
+ "notheme: %ld\n\tnotls: %ld\n\tnowallp: %ld\n\tnowdrag: "
+ "%ld\n\t"
+ "password: %s\n\tpcb: %s\n\tperf: %ld\n\tport: "
+ "%ld\n\tuser: %s\n\t"
+ "width: %ld",
+ _i->my_settings.fntlm.value, _i->my_settings.height.value,
+ _i->my_settings.host.value, _i->my_settings.nomani.value,
+ _i->my_settings.nonla.value, _i->my_settings.notheme.value,
+ _i->my_settings.notls.value, _i->my_settings.nowallp.value,
+ _i->my_settings.nowdrag.value,
+ _i->my_settings.password.value, _i->my_settings.pcb.value,
+ _i->my_settings.perf.value, _i->my_settings.port.value,
+ _i->my_settings.user.value, _i->my_settings.width.value);
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+#endif /* DEBUG */
+ if (!_i->my_settings.host.value || !_i->my_settings.host.value[0])
+ {
+ const char *msg = "rdp_module: host is not set";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_param_error", _i->task_info);
+ return false;
+ }
+ if (!_i->my_settings.port.value)
+ {
+ const char *msg = "rdp_module: port is not set";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_param_error", _i->task_info);
+ return false;
+ }
+ if (!_i->my_settings.user.value || !_i->my_settings.user.value[0])
+ {
+ const char *msg = "rdp_module: user is not set";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_param_error", _i->task_info);
+ return false;
+ }
+ /* password can be optional ? */
+ if (freerdp_connect(_i->instance))
+ {
+ int fdcount = 0, i;
+ HANDLE *fds[256];
+ ZeroMemory(fds, sizeof(fds));
+
+ struct ev_loop *loop
+ = _i->core->api_core->get_libev_loop(_i->task_info);
+ fdcount = freerdp_get_event_handles(
+ _i->instance->context, (HANDLE *)fds, 256);
+ if (!fdcount)
+ {
+ const char *msg
+ = "Failed to get FreeRDP file descriptors";
+ _i->core->api_utils->log_msg((const uint8_t *)msg,
+ strlen(msg), wrdp_log_level_error, 0);
+ _i->core->api_msgs->send_error_msg(
+ "rdp_module_internal_error", _i->task_info);
+ return false;
+ }
+ _i->rdp_fd_count = fdcount;
+ for (i = 0; i < fdcount; ++i)
+ {
+ ULONG type;
+ WINPR_HANDLE *phandle = 0;
+ int fd = 0;
+ if (!winpr_Handle_GetInfo(fds[i], &type, &phandle))
+ {
+ /* TODO: assume what this is not fatal */
+ const char *msg = "winpr_Handle_GetInfo failed";
+ _i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg),
+ wrdp_log_level_error, 0);
+ continue;
+ }
+ fd = winpr_Handle_getFd(phandle);
+
+ /* TODO: check type here, and set EV_WRITE if needed
+ * for now watch only for read available event*/
+ ev_io_init(
+ &(_i->rdp_fd[i]), rdp_fd_readable_cb, fd, EV_READ);
+ _i->rdp_fd[i].data = internals;
+ ev_io_start(loop, &(_i->rdp_fd[i]));
+ }
+ return true;
+ }
+ {
+ const char *msg = freerdp_get_last_error_name(
+ freerdp_get_last_error(_i->instance->context));
+ _i->core->api_msgs->send_error_msg(msg, _i->task_info);
+ }
+
+ return false;
+}
+
+static BOOL
+rdp_pre_connect(freerdp *instance)
+{
+ rdpSettings *settings = instance->context->settings;
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+
+ RegisterUpdate(instance);
+ RegisterPrimary(instance);
+
+ /* looks like this settings reducing artifacts level */
+ settings->OrderSupport[NEG_PATBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_SCRBLT_INDEX] = FALSE;
+
+ /* causing random disconnects */
+ settings->OrderSupport[NEG_ATEXTOUT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE;
+ settings->NoBitmapCompressionHeader = TRUE;
+ settings->BitmapCompressionDisabled = TRUE;
+ settings->BitmapCacheEnabled = FALSE;
+ settings->BrushSupportLevel = 0;
+
+ /* causing connection failure on recent versions of freerdp */
+ settings->OffscreenSupportLevel = 0;
+ settings->OffscreenCacheSize = 0;
+
+ /* following required at least on windows 7 */
+ settings->OrderSupport[NEG_MEMBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_MEM3BLT_INDEX] = FALSE;
+
+ /* following required for windows 2012,2016 */
+ settings->FastPathOutput = 1;
+
+ /* testing */
+ /* wsgate settings */
+ // settings->ColorDepth = 16;
+ // settings->FrameAcknowledge = 1;
+ // settings->LargePointerFlag = 1;
+ // settings->BitmapCacheV3Enabled = FALSE;
+ // settings->BitmapCachePersistEnabled = FALSE;
+ // settings->OrderSupport[NEG_DSTBLT_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_PATBLT_INDEX] = FALSE; // XXX
+ // settings->OrderSupport[NEG_SCRBLT_INDEX] = FALSE; // XXX
+ // settings->OrderSupport[NEG_OPAQUE_RECT_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_MULTIDSTBLT_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MULTIPATBLT_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MULTISCRBLT_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MULTIOPAQUERECT_INDEX] = FALSE; //
+ //XXX settings->OrderSupport[NEG_MULTI_DRAWNINEGRID_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_LINETO_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_POLYLINE_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_MEMBLT_V2_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_MEM3BLT_V2_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_SAVEBITMAP_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_GLYPH_INDEX_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_FAST_INDEX_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_FAST_GLYPH_INDEX] = TRUE;
+ // settings->OrderSupport[NEG_POLYGON_SC_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_POLYGON_CB_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_ELLIPSE_SC_INDEX] = FALSE;
+ // settings->OrderSupport[NEG_ELLIPSE_CB_INDEX] = FALSE;
+ // settings->GlyphSupportLevel = GLYPH_SUPPORT_NONE;
+
+ PubSub_SubscribeChannelConnected(
+ instance->context->pubSub, rdp_on_channel_connected);
+ PubSub_SubscribeChannelDisconnected(
+ instance->context->pubSub, rdp_on_channel_disconnected);
+
+ freerdp_client_load_addins(instance->context->channels, settings);
+ instance->context->cache = cache_new(instance->context);
+
+ c->my_internals->clrconv
+ = freerdp_clrconv_new(CLRCONV_ALPHA | CLRCONV_INVERT);
+
+ if (!instance->context->cache)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL
+rdp_post_connect(freerdp *instance)
+{
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+ my_rdp_clipboard *clip = 0;
+ rdpPointer p;
+ memset(&p, 0, sizeof(p));
+ p.size = sizeof(my_rdp_pointer);
+ register_pointer(&p);
+ graphics_register_pointer(instance->context->graphics, &p);
+ pointer_cache_register_callbacks(instance->context->update);
+
+ clip = rdp_clipboard_new(c);
+ if (!clip)
+ {
+ const char *msg = "rdp_module: \"rdp_clipboard_new\" failed";
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return FALSE;
+ }
+ c->clipboard = clip;
+
+ c->my_internals->conn_state = rdp_conn_state_connected;
+
+ {
+ const char *msg
+ = "rdp_module: \"C:RDP session connection started.\"";
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0);
+ }
+
+ c->my_internals->core->api_msgs->send_text_msg(
+ "C:RDP session connection started.", c->my_internals->task_info);
+
+ instance->context->update->DesktopResize(instance->context);
+
+ return TRUE;
+}
+
+static DWORD
+rdp_verify_certificate(freerdp *instance, const char *host, UINT16 port,
+ const char *common_name, const char *subject, const char *issuer,
+ const char *fingerprint, DWORD flags)
+{
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+ char buf[512];
+ snprintf(buf, 511,
+ "Certificate details:\n\tSubject: %s\n\tIssuer:"
+ " %s\n\tThumbprint: %s\n"
+ "The above X.509 certificate could not be verified, possibly "
+ "because you do not have "
+ "the CA certificate in your certificate store, or the "
+ "certificate has expired. "
+ "Please look at the OpenSSL documentation on how to add a "
+ "private CA to the store.",
+ subject, issuer, fingerprint);
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_debug, 0);
+
+ return TRUE; /* ?? */
+}
+
+/*static int
+rdp_receive_channel_data (freerdp* instance,
+ UINT16 channelId,
+ BYTE* data,
+ int size,
+ int flags,
+ int total_size)
+{
+ return freerdp_channels_data (
+ instance, channelId, data, size, flags, total_size);
+}*/
+
+static BOOL
+rdp_context_new(freerdp *instance, rdpContext *context)
+{
+ return TRUE;
+}
+
+static void
+rdp_context_free(freerdp *instance, rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (context->cache)
+ {
+ cache_free(context->cache);
+ context->cache = NULL;
+ }
+ if (c->my_internals->clrconv)
+ {
+ freerdp_clrconv_free(c->my_internals->clrconv);
+ c->my_internals->clrconv = NULL;
+ }
+}
+
+void
+rdp_register_base(freerdp *r)
+{
+ r->PreConnect = rdp_pre_connect;
+ r->PostConnect = rdp_post_connect;
+ r->VerifyCertificateEx = rdp_verify_certificate;
+ // r->ReceiveChannelData = rdp_receive_channel_data;
+ r->ContextNew = rdp_context_new;
+ r->ContextFree = rdp_context_free;
+}
diff --git a/src/rdp/rdp_impl.h b/src/rdp/rdp_impl.h
new file mode 100644
index 0000000..0e561ed
--- /dev/null
+++ b/src/rdp/rdp_impl.h
@@ -0,0 +1,127 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <ev.h>
+
+#include <freerdp/freerdp.h>
+#include <winpr/clipboard.h>
+#include <freerdp/client/cliprdr.h>
+
+#define CLRCONV_ALPHA 1
+#define CLRCONV_INVERT 2
+/* if defined RGB555 format is used when rendering with a 16-bit frame buffer
+ */
+#define CLRCONV_RGB555 4
+/* Supported Internal Buffer Formats */
+#define CLRBUF_16BPP 8
+#define CLRBUF_32BPP 32
+
+struct _CLRCONV
+{
+ int alpha;
+ int invert;
+ int rgb555;
+ rdpPalette *palette;
+};
+typedef struct _CLRCONV CLRCONV;
+typedef CLRCONV *HCLRCONV;
+
+/* #define TAG CLIENT_TAG ("RDP")*/
+
+typedef struct
+{
+ /* TODO: make variables list instead of static set */
+ backend_setting_str host, pcb, user, password,
+ /* new settings from freerdp header:*/
+ Domain, PasswordHash, AcceptedCert, ClientHostname, ClientProductId,
+ AlternateShell, ShellWorkingDirectory, ClientAddress, ClientDir,
+ DynamicDSTTimeZoneKeyName, RemoteAssistanceSessionId,
+ RemoteAssistancePassStub, RemoteAssistancePassword,
+ RemoteAssistanceRCTicket, AuthenticationServiceClass,
+ AllowedTlsCiphers, NtlmSamFile, PreconnectionBlob,
+ RedirectionAcceptedCert, KerberosKdc, KerberosRealm,
+ CertificateName, CertificateFile, PrivateKeyFile,
+ CertificateContent, PrivateKeyContent, WindowTitle, WmClass,
+ ComputerName, ConnectionFile, AssistanceFile, HomePath, ConfigPath,
+ CurrentPath, DumpRemoteFxFile, PlayRemoteFxFile, GatewayHostname,
+ GatewayUsername, GatewayPassword, GatewayDomain, GatewayAccessToken,
+ GatewayAcceptedCert, ProxyHostname, RemoteApplicationName,
+ RemoteApplicationIcon, RemoteApplicationProgram,
+ RemoteApplicationFile, RemoteApplicationGuid,
+ RemoteApplicationCmdLine, ImeFileName, DrivesToRedirect,
+ ActionScript;
+ backend_setting_int port, perf, fntlm, nowallp, nowdrag, nomani,
+ notheme, nonla, notls, width, height,
+ /* new settings from freerdp header:*/
+ ServerMode, WaitForOutputBufferFlush, MaxTimeInCheckLoop,
+ DesktopWidth, DesktopHeight, Workarea, Fullscreen, GrabKeyboard,
+ Decorations, RdpVersion, ColorDepth, ExtSecurity, NlaSecurity,
+ TlsSecurity, RdpSecurity, NegotiateSecurityLayer,
+ RestrictedAdminModeRequired, MstscCookieMode, CookieMaxLength,
+ ClientBuild, KeyboardType, KeyboardSubType, KeyboardFunctionKey,
+ KeyboardLayout, UseRdpSecurityLayer, SaltedChecksum, ServerPort,
+ GatewayPort, DesktopResize, ToggleFullscreen, Floatbar, DesktopPosX,
+ DesktopPosY, SoftwareGdi, UnmapButtons, PerformanceFlags,
+ AllowFontSmoothing, AllowDesktopComposition, DisableWallpaper,
+ DisableFullWindowDrag, DisableMenuAnims, DisableThemes,
+ ConnectionType, EncryptionMethods, EncryptionLevel, FIPSMode,
+ CompressionEnabled, LogonNotify, BrushSupportLevel,
+ RemoteApplicationMode, TcpAckTimeout;
+} my_rdp_settings;
+
+typedef struct
+{
+ rdpPointer pointer;
+ uint32_t id;
+} my_rdp_pointer;
+
+struct rdp_internals_s;
+typedef struct rdp_internals_s rdp_internals;
+
+struct my_rdp_clipboard_s;
+typedef struct my_rdp_clipboard_s my_rdp_clipboard;
+
+struct my_rdp_ft_s;
+typedef struct my_rdp_ft_s my_rdp_ft;
+
+typedef struct
+{
+ rdpContext context;
+ my_rdp_clipboard *clipboard;
+ my_rdp_ft *ft_to_client, *ft_to_server;
+ rdp_internals *my_internals;
+} my_rdp_context;
+
+struct rdp_internals_s
+{
+ freerdp *instance;
+ my_rdp_context *context;
+
+ rdpSettings *settings;
+
+ void *task_info;
+ wrdp_core_exports *core;
+ wrdp_backend_module *backend;
+
+ my_rdp_settings my_settings;
+ rdp_connection_state conn_state;
+
+ /* libev watchers */
+ /* TODO: dynamic allocation */
+ ev_io rdp_fd[32];
+ int rdp_fd_count;
+
+ HCLRCONV clrconv;
+ uint32_t m_ptrId;
+};
+
+bool rdp_init_internals(void *internals);
+
+bool rdp_connect(void *internals);
+
+bool rdp_init_settings(void *internals);
diff --git a/src/rdp/rdp_io.c b/src/rdp/rdp_io.c
new file mode 100644
index 0000000..5754997
--- /dev/null
+++ b/src/rdp/rdp_io.c
@@ -0,0 +1,1492 @@
+#include <freerdp/client/channels.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/file.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/log.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_png.h"
+
+const UINT32 ASCII_TO_SCANCODE[256] = {
+ 0, /* 0 */
+ 0, /* 1 */
+ 0, /* 2 */
+ 0, /* 3 */
+ 0, /* 4 */
+ 0, /* 5 */
+ 0, /* 6 */
+ 0, /* 7 */
+ RDP_SCANCODE_BACKSPACE, /* 8 */
+ RDP_SCANCODE_TAB, /* 9 */
+ VK_KEY_D, /* 10 */
+ VK_KEY_F, /* 11 */
+ VK_KEY_H, /* 12 */
+ RDP_SCANCODE_RETURN, /* 13 */
+ RDP_SCANCODE_BACKSPACE, /* 14 */
+ VK_KEY_X, /* 15 */
+ RDP_SCANCODE_LSHIFT, /* 16 */
+ RDP_SCANCODE_LCONTROL, /* 17 */
+ RDP_SCANCODE_LMENU, /* 18 */
+ RDP_SCANCODE_PAUSE, /* 19 */
+ RDP_SCANCODE_CAPSLOCK, /* 20 */
+ VK_KEY_W, /* 21 */
+ VK_KEY_E, /* 22 */
+ VK_KEY_R, /* 23 */
+ VK_KEY_Y, /* 24 */
+ VK_KEY_A, /* 25 */
+ VK_KEY_1, /* 26 */
+ RDP_SCANCODE_ESCAPE, /* 27 */
+ VK_KEY_3, /* 28 */
+ VK_KEY_4, /* 29 */
+ VK_KEY_6, /* 30 */
+ VK_KEY_5, /* 31 */
+ RDP_SCANCODE_SPACE, /* 32 */
+ RDP_SCANCODE_PRIOR, /* 33 */
+ RDP_SCANCODE_NEXT, /* 34 */
+ RDP_SCANCODE_END, /* 35 */
+ RDP_SCANCODE_HOME, /* 36 */
+ RDP_SCANCODE_LEFT, /* 37 */
+ RDP_SCANCODE_UP, /* 38 */
+ RDP_SCANCODE_RIGHT, /* 39 */
+ RDP_SCANCODE_DOWN, /* 40 */
+ RDP_SCANCODE_KEY_0, /* 41 */
+ RDP_SCANCODE_MULTIPLY, /* 42 */
+ RDP_SCANCODE_ADD, /* 43 */
+ RDP_SCANCODE_PRINTSCREEN, /* 44 */
+ RDP_SCANCODE_INSERT, /* 45 */
+ RDP_SCANCODE_DELETE, /* 46 */
+ RDP_SCANCODE_DIVIDE, /* 47 */
+ RDP_SCANCODE_KEY_0, /* 48 */
+ RDP_SCANCODE_KEY_1, /* 49 */
+ RDP_SCANCODE_KEY_2, /* 50 */
+ RDP_SCANCODE_KEY_3, /* 51 */
+ RDP_SCANCODE_KEY_4, /* 52 */
+ RDP_SCANCODE_KEY_5, /* 53 */
+ RDP_SCANCODE_KEY_6, /* 54 */
+ RDP_SCANCODE_KEY_7, /* 55 */
+ RDP_SCANCODE_KEY_8, /* 56 */
+ RDP_SCANCODE_KEY_9, /* 57 */
+ RDP_SCANCODE_OEM_1, /* 58 */
+ RDP_SCANCODE_OEM_1, /* 59 */
+ RDP_SCANCODE_OEM_COMMA, /* 60 */
+ RDP_SCANCODE_OEM_PLUS, /* 61 */
+ RDP_SCANCODE_OEM_PERIOD, /* 62 */
+ RDP_SCANCODE_DIVIDE, /* 63 */
+ RDP_SCANCODE_KEY_2, /* 64 */
+ RDP_SCANCODE_KEY_A, /* 65 */
+ RDP_SCANCODE_KEY_B, /* 66 */
+ RDP_SCANCODE_KEY_C, /* 67 */
+ RDP_SCANCODE_KEY_D, /* 68 */
+ RDP_SCANCODE_KEY_E, /* 69 */
+ RDP_SCANCODE_KEY_F, /* 70 */
+ RDP_SCANCODE_KEY_G, /* 71 */
+ RDP_SCANCODE_KEY_H, /* 72 */
+ RDP_SCANCODE_KEY_I, /* 73 */
+ RDP_SCANCODE_KEY_J, /* 74 */
+ RDP_SCANCODE_KEY_K, /* 75 */
+ RDP_SCANCODE_KEY_L, /* 76 */
+ RDP_SCANCODE_KEY_M, /* 77 */
+ RDP_SCANCODE_KEY_N, /* 78 */
+ RDP_SCANCODE_KEY_O, /* 79 */
+ RDP_SCANCODE_KEY_P, /* 80 */
+ RDP_SCANCODE_KEY_Q, /* 81 */
+ RDP_SCANCODE_KEY_R, /* 82 */
+ RDP_SCANCODE_KEY_S, /* 83 */
+ RDP_SCANCODE_KEY_T, /* 84 */
+ RDP_SCANCODE_KEY_U, /* 85 */
+ RDP_SCANCODE_KEY_V, /* 86 */
+ RDP_SCANCODE_KEY_W, /* 87 */
+ RDP_SCANCODE_KEY_X, /* 88 */
+ RDP_SCANCODE_KEY_Y, /* 89 */
+ RDP_SCANCODE_KEY_Z, /* 90 */
+ RDP_SCANCODE_LWIN, /* 91 */
+ RDP_SCANCODE_RWIN, /* 92 */
+ RDP_SCANCODE_APPS, /* 93 */
+ RDP_SCANCODE_KEY_6, /* 94 */
+ RDP_SCANCODE_OEM_MINUS, /* 95 */
+ RDP_SCANCODE_NUMPAD0, /* 96 */
+ RDP_SCANCODE_NUMPAD1, /* 97 */
+ RDP_SCANCODE_NUMPAD2, /* 98 */
+ RDP_SCANCODE_NUMPAD3, /* 99 */
+ RDP_SCANCODE_NUMPAD4, /* 100 */
+ RDP_SCANCODE_NUMPAD5, /* 101 */
+ RDP_SCANCODE_NUMPAD6, /* 102 */
+ RDP_SCANCODE_NUMPAD7, /* 103 */
+ RDP_SCANCODE_NUMPAD8, /* 104 */
+ RDP_SCANCODE_NUMPAD9, /* 105 */
+ RDP_SCANCODE_MULTIPLY, /* 106 */
+ RDP_SCANCODE_ADD, /* 107 */
+ 0, /* 108 */
+ RDP_SCANCODE_SUBTRACT, /* 109 */
+ RDP_SCANCODE_DELETE, /* 110 */
+ RDP_SCANCODE_DIVIDE, /* 111 */
+ RDP_SCANCODE_F1, /* 112 */
+ RDP_SCANCODE_F2, /* 113 */
+ RDP_SCANCODE_F3, /* 114 */
+ RDP_SCANCODE_F4, /* 115 */
+ RDP_SCANCODE_F5, /* 116 */
+ RDP_SCANCODE_F6, /* 117 */
+ RDP_SCANCODE_F7, /* 118 */
+ RDP_SCANCODE_F8, /* 119 */
+ RDP_SCANCODE_F9, /* 120 */
+ RDP_SCANCODE_F10, /* 121 */
+ RDP_SCANCODE_F11, /* 122 */
+ RDP_SCANCODE_F12, /* 123 */
+ RDP_SCANCODE_OEM_5, /* 124 */
+ RDP_SCANCODE_OEM_6, /* 125 */
+ VK_F4, /* 126 */
+ VK_END, /* 127 */
+ VK_F2, /* 128 */
+ VK_NEXT, /* 129 */
+ VK_F1, /* 130 */
+ VK_LEFT, /* 131 */
+ VK_RIGHT, /* 132 */
+ VK_DOWN, /* 133 */
+ VK_UP, /* 134 */
+ 0, /* 135 */
+ 0, /* 136 */
+ 0, /* 137 */
+ 0, /* 138 */
+ 0, /* 139 */
+ 0, /* 140 */
+ 0, /* 141 */
+ 0, /* 142 */
+ 0, /* 143 */
+ RDP_SCANCODE_NUMLOCK, /* 144 */
+ RDP_SCANCODE_SCROLLLOCK, /* 145 */
+ 0, /* 146 */
+ 0, /* 147 */
+ 0, /* 148 */
+ 0, /* 149 */
+ 0, /* 150 */
+ 0, /* 151 */
+ 0, /* 152 */
+ 0, /* 153 */
+ 0, /* 154 */
+ 0, /* 155 */
+ 0, /* 156 */
+ 0, /* 157 */
+ 0, /* 158 */
+ 0, /* 159 */
+ 0, /* 160 */
+ 0, /* 161 */
+ 0, /* 162 */
+ 0, /* 163 */
+ 0, /* 164 */
+ 0, /* 165 */
+ 0, /* 166 */
+ 0, /* 167 */
+ 0, /* 168 */
+ 0, /* 169 */
+ 0, /* 170 */
+ 0, /* 171 */
+ 0, /* 172 */
+ RDP_SCANCODE_OEM_MINUS, /* 173 */
+ 0, /* 174 */
+ 0, /* 175 */
+ 0, /* 176 */
+ 0, /* 177 */
+ 0, /* 178 */
+ 0, /* 179 */
+ 0, /* 180 */
+ 0, /* 181 */
+ 0, /* 182 */
+ 0, /* 183 */
+ 0, /* 184 */
+ 0, /* 185 */
+ RDP_SCANCODE_OEM_1, /* 186 */
+ RDP_SCANCODE_OEM_PLUS, /* 187 */
+ RDP_SCANCODE_OEM_COMMA, /* 188 */
+ RDP_SCANCODE_OEM_MINUS, /* 189 */
+ RDP_SCANCODE_OEM_PERIOD, /* 190 */
+ RDP_SCANCODE_OEM_2, /* 191 */
+ RDP_SCANCODE_OEM_3, /* 192 */
+ 0, /* 193 */
+ 0, /* 194 */
+ 0, /* 195 */
+ 0, /* 196 */
+ 0, /* 197 */
+ 0, /* 198 */
+ 0, /* 199 */
+ 0, /* 200 */
+ 0, /* 201 */
+ 0, /* 202 */
+ 0, /* 203 */
+ 0, /* 204 */
+ 0, /* 205 */
+ 0, /* 206 */
+ 0, /* 207 */
+ 0, /* 208 */
+ 0, /* 209 */
+ 0, /* 210 */
+ 0, /* 211 */
+ 0, /* 212 */
+ 0, /* 213 */
+ 0, /* 214 */
+ 0, /* 215 */
+ 0, /* 216 */
+ 0, /* 217 */
+ 0, /* 218 */
+ RDP_SCANCODE_OEM_4, /* 219 */
+ RDP_SCANCODE_OEM_5, /* 220 */
+ RDP_SCANCODE_OEM_6, /* 221 */
+ RDP_SCANCODE_OEM_7, /* 222 */
+ 0, /* 223 */
+ 0, /* 224 */
+ 0, /* 225 */
+ 0, /* 226 */
+ 0, /* 227 */
+ 0, /* 228 */
+ 0, /* 229 */
+ 0, /* 230 */
+ 0, /* 231 */
+ 0, /* 232 */
+ 0, /* 233 */
+ 0, /* 234 */
+ 0, /* 235 */
+ 0, /* 236 */
+ 0, /* 237 */
+ 0, /* 238 */
+ 0, /* 239 */
+ 0, /* 240 */
+ 0, /* 241 */
+ 0, /* 242 */
+ 0, /* 243 */
+ 0, /* 244 */
+ 0, /* 245 */
+ 0, /* 246 */
+ 0, /* 247 */
+ 0, /* 248 */
+ 0, /* 249 */
+ 0, /* 250 */
+ 0, /* 251 */
+ 0, /* 252 */
+ 0, /* 253 */
+ 0, /* 254 */
+ 0 /* 255 */
+};
+
+#define RGB_555_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 3 & ~0x7) | (_g >> 2); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define RGB_565_888(_r, _g, _b) \
+ _r = (_r << 3 & ~0x7) | (_r >> 2); \
+ _g = (_g << 2 & ~0x3) | (_g >> 4); \
+ _b = (_b << 3 & ~0x7) | (_b >> 2);
+#define GetRGB_555(_r, _g, _b, _p) \
+ _r = (_p & 0x7C00) >> 10; \
+ _g = (_p & 0x3E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB_565(_r, _g, _b, _p) \
+ _r = (_p & 0xF800) >> 11; \
+ _g = (_p & 0x7E0) >> 5; \
+ _b = (_p & 0x1F);
+#define GetRGB15(_r, _g, _b, _p) \
+ GetRGB_555(_r, _g, _b, _p); \
+ RGB_555_888(_r, _g, _b);
+#define GetRGB16(_r, _g, _b, _p) \
+ GetRGB_565(_r, _g, _b, _p); \
+ RGB_565_888(_r, _g, _b);
+#define RGB24(_r, _g, _b) ((_r << 16) | (_g << 8) | _b)
+#define GetRGB24(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define GetRGB32(_r, _g, _b, _p) \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+#define ARGB32(_a, _r, _g, _b) (_a << 24) | (_r << 16) | (_g << 8) | _b
+#define RGB16(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define RGB15(_r, _g, _b) \
+ (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) \
+ | ((_b >> 3) & 0x1F)
+#define GetARGB32(_a, _r, _g, _b, _p) \
+ _a = (_p & 0xFF000000) >> 24; \
+ _r = (_p & 0xFF0000) >> 16; \
+ _g = (_p & 0xFF00) >> 8; \
+ _b = (_p & 0xFF);
+
+static HCLRCONV
+freerdp_clrconv_new(UINT32 flags)
+{
+ HCLRCONV clrconv;
+
+ clrconv = (CLRCONV *)calloc(1, sizeof(CLRCONV));
+
+ if (!clrconv)
+ return NULL;
+
+ clrconv->alpha = (flags & CLRCONV_ALPHA) ? TRUE : FALSE;
+ clrconv->invert = (flags & CLRCONV_INVERT) ? TRUE : FALSE;
+ clrconv->rgb555 = (flags & CLRCONV_RGB555) ? TRUE : FALSE;
+
+ clrconv->palette = (rdpPalette *)calloc(1, sizeof(rdpPalette));
+
+ if (!clrconv->palette)
+ {
+ free(clrconv);
+ return NULL;
+ }
+
+ return clrconv;
+}
+
+static void
+freerdp_clrconv_free(HCLRCONV clrconv)
+{
+ if (clrconv)
+ {
+ free(clrconv->palette);
+ free(clrconv);
+ }
+}
+
+static int
+freerdp_get_pixel(BYTE *data, int x, int y, int width, int height, int bpp)
+{
+ int start;
+ int shift;
+ UINT16 *src16;
+ UINT32 *src32;
+ int red, green, blue;
+
+ switch (bpp)
+ {
+ case 1:
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ return (data[start] & (0x80 >> shift)) != 0;
+ case 8:
+ return data[y * width + x];
+ case 15:
+ case 16:
+ src16 = (UINT16 *)data;
+ return src16[y * width + x];
+ case 24:
+ data += y * width * 3;
+ data += x * 3;
+ red = data[0];
+ green = data[1];
+ blue = data[2];
+ return RGB24(red, green, blue);
+ case 32:
+ src32 = (UINT32 *)data;
+ return src32[y * width + x];
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void
+freerdp_set_pixel(
+ BYTE *data, int x, int y, int width, int height, int bpp, int pixel)
+{
+ int start;
+ int shift;
+ int *dst32;
+
+ if (bpp == 1)
+ {
+ width = (width + 7) / 8;
+ start = (y * width) + x / 8;
+ shift = x % 8;
+ if (pixel)
+ data[start] = data[start] | (0x80 >> shift);
+ else
+ data[start] = data[start] & ~(0x80 >> shift);
+ }
+ else if (bpp == 32)
+ {
+ dst32 = (int *)data;
+ dst32[y * width + x] = pixel;
+ }
+}
+
+static void
+freerdp_color_split_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ *red = *green = *blue = 0;
+ *alpha = (clrconv->alpha) ? 0xFF : 0x00;
+
+ switch (bpp)
+ {
+ case 32:
+ if (clrconv->alpha)
+ {
+ GetARGB32(*alpha, *red, *green, *blue, *color);
+ }
+ else
+ {
+ GetRGB32(*red, *green, *blue, *color);
+ }
+ break;
+
+ case 24:
+ GetRGB24(*red, *green, *blue, *color);
+ break;
+
+ case 16:
+ GetRGB16(*red, *green, *blue, *color);
+ break;
+
+ case 15:
+ GetRGB15(*red, *green, *blue, *color);
+ break;
+
+ case 8:
+ *color &= 0xFF;
+ *red = clrconv->palette->entries[*color].red;
+ *green = clrconv->palette->entries[*color].green;
+ *blue = clrconv->palette->entries[*color].blue;
+ break;
+
+ case 1:
+ if (*color != 0)
+ {
+ *red = 0xFF;
+ *green = 0xFF;
+ *blue = 0xFF;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+freerdp_color_make_rgb(UINT32 *color, int bpp, BYTE *red, BYTE *green,
+ BYTE *blue, BYTE *alpha, HCLRCONV clrconv)
+{
+ switch (bpp)
+ {
+ case 32:
+ *color = ARGB32(*alpha, *red, *green, *blue);
+ break;
+
+ case 24:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 16:
+ if (clrconv->rgb555)
+ {
+ *color = RGB15(*red, *green, *blue);
+ }
+ else
+ {
+ *color = RGB16(*red, *green, *blue);
+ }
+ break;
+
+ case 15:
+ *color = RGB15(*red, *green, *blue);
+ break;
+
+ case 8:
+ *color = RGB24(*red, *green, *blue);
+ break;
+
+ case 1:
+ if ((*red != 0) || (*green != 0) || (*blue != 0))
+ *color = 1;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static UINT32
+freerdp_color_convert_rgb(
+ UINT32 srcColor, int srcBpp, int dstBpp, HCLRCONV clrconv)
+{
+ BYTE red = 0;
+ BYTE green = 0;
+ BYTE blue = 0;
+ BYTE alpha = 0xFF;
+ UINT32 dstColor = 0;
+
+ freerdp_color_split_rgb(
+ &srcColor, srcBpp, &red, &green, &blue, &alpha, clrconv);
+ freerdp_color_make_rgb(
+ &dstColor, dstBpp, &red, &green, &blue, &alpha, clrconv);
+
+ return dstColor;
+}
+
+static void
+freerdp_alpha_cursor_convert(BYTE *alphaData, BYTE *xorMask, BYTE *andMask,
+ int width, int height, int bpp, HCLRCONV clrconv)
+{
+ UINT32 xorPixel;
+ UINT32 andPixel;
+ UINT32 x, y, jj;
+
+ for (y = 0; y < height; y++)
+ {
+ jj = (bpp == 1) ? y : (height - 1) - y;
+
+ for (x = 0; x < width; x++)
+ {
+ xorPixel = freerdp_get_pixel(
+ xorMask, x, jj, width, height, bpp);
+ xorPixel = freerdp_color_convert_rgb(
+ xorPixel, bpp, 32, clrconv);
+ andPixel = freerdp_get_pixel(
+ andMask, x, jj, width, height, 1);
+
+ if (andPixel)
+ {
+ if ((xorPixel & 0xFFFFFF) == 0xFFFFFF)
+ {
+ /* use pattern (not solid black) for xor
+ * area */
+ xorPixel = (x & 1) == (y & 1);
+ xorPixel = xorPixel ? 0xFFFFFF : 0;
+ xorPixel |= 0xFF000000;
+ }
+ else if (xorPixel == 0xFF000000)
+ {
+ xorPixel = 0;
+ }
+ }
+
+ freerdp_set_pixel(
+ alphaData, x, y, width, height, 32, xorPixel);
+ }
+ }
+}
+
+static void
+freerdp_bitmap_flip(BYTE *src, BYTE *dst, int scanLineSz, int height)
+{
+ int i;
+
+ BYTE *bottomLine = dst + (scanLineSz * (height - 1));
+ BYTE *topLine = src;
+
+ /* Special processing if called for flip-in-place. */
+ if (src == dst)
+ {
+ /* Allocate a scanline buffer.
+ * (FIXME: malloc / xfree below should be replaced by "get/put
+ * scanline buffer from a pool/Q of fixed buffers" to reuse
+ * fixed size buffers (of max scanline size (or adaptative?) )
+ * -- would be much faster).
+ */
+ BYTE *tmpBfr = (BYTE *)_aligned_malloc(scanLineSz, 16);
+ int half = height / 2;
+ /* Flip buffer in place by line permutations through the temp
+ * scan line buffer.
+ * Note that if height has an odd number of line, we don't need
+ * to move the center scanline anyway.
+ * Also note that in place flipping takes three memcpy() calls
+ * to process two scanlines while src to distinct dest would
+ * only requires two memcpy() calls for two scanlines.
+ */
+ height--;
+ for (i = 0; i < half; i++)
+ {
+ CopyMemory(tmpBfr, topLine, scanLineSz);
+ CopyMemory(topLine, bottomLine, scanLineSz);
+ CopyMemory(bottomLine, tmpBfr, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ height--;
+ }
+ _aligned_free(tmpBfr);
+ }
+ /* Flip from source buffer to destination buffer. */
+ else
+ {
+
+ for (i = 0; i < height; i++)
+ {
+ CopyMemory(bottomLine, topLine, scanLineSz);
+ topLine += scanLineSz;
+ bottomLine -= scanLineSz;
+ }
+ }
+}
+
+static BYTE *
+freerdp_image_flip(BYTE *srcData, BYTE *dstData, int width, int height, int bpp)
+{
+ int scanline;
+
+ scanline = width * ((bpp + 7) / 8);
+
+ if (!dstData)
+ dstData = (BYTE *)_aligned_malloc(
+ width * height * ((bpp + 7) / 8), 16);
+
+ if (!dstData)
+ {
+ return NULL;
+ }
+
+ freerdp_bitmap_flip(srcData, dstData, scanline, height);
+
+ return dstData;
+}
+
+/* outgoing rdp protocol to client implementation */
+
+static BOOL
+Pointer_New(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ HCLRCONV hclrconv = c->my_internals->clrconv;
+ size_t psize = pointer->width * pointer->height * 4;
+
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ p->id = c->my_internals->m_ptrId++;
+ uint8_t *pixels = malloc(sizeof(uint8_t) * psize);
+ memset(pixels, 0, psize);
+ if ((pointer->andMaskData != 0) && (pointer->xorMaskData != 0))
+ {
+ //XXX
+ freerdp_alpha_cursor_convert(pixels, pointer->xorMaskData,
+ pointer->andMaskData, pointer->width, pointer->height,
+ pointer->xorBpp, hclrconv);
+ }
+
+ //check if the cursor is fully transparent
+ bool transparent = TRUE;
+ for (int y = 0; y < pointer->height; y++)
+ {
+ for (int x = 0; x < pointer->width; x++)
+ {
+ if (pixels[0 + x * 4 + y * 4 * pointer->width] != 0)
+ {
+ transparent = FALSE;
+ }
+ }
+ }
+ if (transparent)
+ {
+ pixels[3] = 1;
+ }
+
+ rdp_png_buf png_buf;
+ memset(&png_buf, 0, sizeof(png_buf));
+ png_buf.buf_size
+ = (pointer->width * pointer->height * sizeof(uint32_t))
+ + 8 /* allocating fullsize 32but argb pixels + png header */;
+ png_buf.buf = malloc(png_buf.buf_size);
+ png_generate_from_argb(
+ pointer->width, pointer->height, pixels, &png_buf);
+
+ struct
+ {
+ unsigned char resL; //2 bytes reserved must always be 0
+ unsigned char resH;
+ unsigned char imgTypeL; //2 bytes image type. 1 = ICO, 2 = CUR
+ unsigned char imgTypeH;
+ unsigned char nbImgL; //2 bytes number of images
+ unsigned char nbImgH;
+ unsigned char width; //1 byte image width in pixels
+ unsigned char height; //1 byte image height in pixels
+ unsigned char nbColors; //1 bytenumber of colors. 0 if not using
+ //a color pallete
+ unsigned char res; //1 byte reserved, should be 0
+ unsigned char xPosL; //2 bytes of hot spot x (for ICO, color
+ //planes, 0 or 1)
+ unsigned char xPosH;
+ unsigned char
+ yPosL; //2 bytes of hot spot y (for ICO bits per pixel)
+ unsigned char yPosH;
+ unsigned char sizeLL; //4 bytes of image size
+ unsigned char sizeLH;
+ unsigned char sizeHL;
+ unsigned char sizeHH;
+ unsigned char offsetLL; //4 bytes of offset of the data
+ unsigned char offsetLH;
+ unsigned char offsetHL;
+ unsigned char offsetHH;
+ } curHeader = {0x00, 0x00, 0x02, 0x00, 0x01, 0x00,
+ (unsigned char)pointer->width, (unsigned char)pointer->height, 0x00,
+ 0x00, (unsigned char)pointer->xPos,
+ (unsigned char)(pointer->xPos >> 8), (unsigned char)pointer->yPos,
+ (unsigned char)(pointer->yPos >> 8), (unsigned char)png_buf.written,
+ (unsigned char)(png_buf.written >> 8),
+ (unsigned char)(png_buf.written >> 16),
+ (unsigned char)(png_buf.written >> 24), 0x16, 0x00, 0x00, 0x00};
+
+ size_t cursor_buf_size = png_buf.written + sizeof(curHeader);
+ uint8_t *cursor_buf = malloc(cursor_buf_size);
+
+ memcpy(cursor_buf, &curHeader, sizeof(curHeader));
+ memcpy(cursor_buf + sizeof(curHeader), png_buf.buf, png_buf.written);
+ free(png_buf.buf);
+
+ /* rdp_cursor_add (p->id, cursor_buf, cursor_buf_size,
+ * c->my_internals); */
+
+ free(pixels);
+ struct
+ {
+ uint32_t id;
+ uint32_t hx;
+ uint32_t hy;
+ } tmp = {p->id, pointer->xPos, pointer->yPos};
+ size_t send_buf_size = sizeof(tmp) + cursor_buf_size;
+ uint8_t *send_buf = malloc(send_buf_size);
+ memcpy(send_buf, &tmp, sizeof(tmp));
+ memcpy(send_buf + sizeof(tmp), cursor_buf, cursor_buf_size);
+ free(cursor_buf);
+ c->my_internals->core->send_ptr_new(
+ send_buf, send_buf_size, c->my_internals->task_info);
+ return TRUE;
+}
+
+static void
+Pointer_Free(rdpContext *context, rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (p->id)
+ {
+ /* rdp_cursor_erase (p->id, c->my_internals); */
+ c->my_internals->core->send_ptr_free((uint8_t *)&(p->id),
+ sizeof(uint32_t), c->my_internals->task_info);
+ }
+}
+
+static BOOL
+Pointer_Set(rdpContext *context, const rdpPointer *pointer)
+{
+ my_rdp_pointer *p = (my_rdp_pointer *)pointer;
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_ptr_set(
+ (uint8_t *)&(p->id), sizeof(uint32_t), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetNull(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_ptr_set_null(c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Pointer_SetDefault(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_ptr_set_default(c->my_internals->task_info);
+ return TRUE;
+}
+
+void
+register_pointer(rdpPointer *p)
+{
+ p->New = Pointer_New;
+ p->Free = Pointer_Free;
+ p->Set = Pointer_Set;
+ p->SetNull = Pointer_SetNull;
+ p->SetDefault = Pointer_SetDefault;
+}
+
+static BOOL
+rdp_context_new(freerdp *instance, rdpContext *context)
+{
+ return TRUE;
+}
+
+static void
+rdp_context_free(freerdp *instance, rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (context->cache)
+ {
+ cache_free(context->cache);
+ context->cache = NULL;
+ }
+ if (c->my_internals->clrconv)
+ {
+ freerdp_clrconv_free(c->my_internals->clrconv);
+ c->my_internals->clrconv = NULL;
+ }
+}
+
+static BOOL
+BeginPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_begin_paint(c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+EndPaint(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ c->my_internals->core->send_end_paint(c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+SetBounds(rdpContext *context, const rdpBounds *bounds)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ rdpBounds lB;
+ if (bounds)
+ {
+ memcpy(&lB, bounds, sizeof(rdpBounds));
+ lB.right++;
+ lB.bottom++;
+ }
+ else
+ {
+ memset(&lB, 0, sizeof(rdpBounds));
+ }
+ c->my_internals->core->send_set_bounds(
+ (uint8_t *)&lB, sizeof(lB), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+Synchronize(rdpContext *context)
+{
+ return TRUE;
+}
+
+static BOOL
+DesktopResize(rdpContext *context)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ char buf[64];
+ snprintf(buf, 63, "R:%dx%d", context->settings->DesktopWidth,
+ context->settings->DesktopHeight);
+ c->my_internals->core->send_text_msg(buf, c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+BitmapUpdate(rdpContext *context, const BITMAP_UPDATE *bitmap)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ int i;
+ BITMAP_DATA *bmd;
+ for (i = 0; i < (int)bitmap->number; i++)
+ {
+ bmd = &bitmap->rectangles[i];
+ struct
+ {
+ uint32_t x;
+ uint32_t y;
+ uint32_t w;
+ uint32_t h;
+ uint32_t dw;
+ uint32_t dh;
+ uint32_t bpp;
+ uint32_t cf;
+ uint32_t sz;
+ } wxbm = {bmd->destLeft, bmd->destTop, bmd->width, bmd->height,
+ bmd->destRight - bmd->destLeft + 1,
+ bmd->destBottom - bmd->destTop + 1, bmd->bitsPerPixel,
+ (uint32_t)bmd->compressed, bmd->bitmapLength};
+ if (!bmd->compressed)
+ {
+ freerdp_image_flip(bmd->bitmapDataStream,
+ bmd->bitmapDataStream, bmd->width, bmd->height,
+ bmd->bitsPerPixel);
+ }
+ size_t buf_size = sizeof(wxbm) + bmd->bitmapLength;
+ uint8_t *buf = malloc(buf_size);
+ memcpy(buf, &wxbm, sizeof(wxbm));
+ memcpy(buf + sizeof(wxbm), bmd->bitmapDataStream,
+ bmd->bitmapLength);
+ c->my_internals->core->send_bitmap(
+ buf, buf_size, c->my_internals->task_info);
+ free(buf);
+ }
+ return TRUE;
+}
+
+static BOOL
+Palette(rdpContext *c, const PALETTE_UPDATE *p)
+{
+ return TRUE;
+}
+
+static BOOL
+PlaySound(rdpContext *c, const PLAY_SOUND_UPDATE *s)
+{
+ return TRUE;
+}
+
+static BOOL
+RefreshRect(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+static BOOL
+SuppressOutput(rdpContext *c, UINT8 hz, const RECTANGLE_16 *r)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceCommand(rdpContext* c, wStream* s)
+{
+ return TRUE;
+}*/
+
+static BOOL
+SurfaceBits(rdpContext *c, const SURFACE_BITS_COMMAND *sbc)
+{
+ return TRUE;
+}
+
+/*static BOOL
+SurfaceFrameMarker(rdpContext* c, SURFACE_FRAME_MARKER* sfm)
+{
+ return TRUE;
+} */
+
+void
+RegisterUpdate(freerdp *rdp)
+{
+ rdp->update->BeginPaint = BeginPaint;
+ rdp->update->EndPaint = EndPaint;
+ rdp->update->SetBounds = SetBounds;
+ rdp->update->Synchronize = Synchronize;
+ rdp->update->DesktopResize = DesktopResize;
+ rdp->update->BitmapUpdate = BitmapUpdate;
+ rdp->update->Palette = Palette;
+ rdp->update->PlaySound = PlaySound;
+ rdp->update->SurfaceBits = SurfaceBits;
+
+ rdp->update->RefreshRect = RefreshRect;
+ rdp->update->SuppressOutput = SuppressOutput;
+}
+
+static BOOL
+DstBlt(rdpContext *c, const DSTBLT_ORDER *_do)
+{
+ return TRUE;
+}
+
+static BOOL
+PatBlt(rdpContext *ctx, PATBLT_ORDER *po)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(po->bRop);
+ if (GDI_BS_SOLID == po->brush.style)
+ {
+ struct
+ {
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ uint32_t fg;
+ uint32_t rop;
+ } tmp = {po->nLeftRect, po->nTopRect, po->nWidth, po->nHeight,
+ ConvertColor(po->foreColor, PIXEL_FORMAT_BGR16,
+ PIXEL_FORMAT_ABGR32,
+ NULL), //XXX
+ rop3};
+ c->my_internals->core->send_pat_blt(
+ (uint8_t *)&tmp, sizeof(tmp), c->my_internals->task_info);
+ }
+ return TRUE;
+}
+
+static BOOL
+ScrBlt(rdpContext *ctx, const SCRBLT_ORDER *sbo)
+{
+ my_rdp_context *c = (my_rdp_context *)ctx;
+ uint32_t rop3 = gdi_rop3_code(sbo->bRop);
+ struct
+ {
+ uint32_t rop;
+ int32_t x;
+ int32_t y;
+ int32_t w;
+ int32_t h;
+ int32_t sx;
+ int32_t sy;
+ } tmp = {rop3, sbo->nLeftRect, sbo->nTopRect, sbo->nWidth, sbo->nHeight,
+ sbo->nXSrc, sbo->nYSrc};
+ c->my_internals->core->send_scr_blt(
+ (uint8_t *)&tmp, sizeof(tmp), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+OpaqueRect(rdpContext *context, const OPAQUE_RECT_ORDER *oro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ OPAQUE_RECT_ORDER oro2 = *oro;
+ oro2.color = ConvertColor(
+ oro2.color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ c->my_internals->core->send_opaque_rect_order((uint8_t *)&oro2,
+ sizeof(OPAQUE_RECT_ORDER), c->my_internals->task_info);
+ return TRUE;
+}
+
+static BOOL
+DrawNineGrid(rdpContext *c, const DRAW_NINE_GRID_ORDER *dngo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiDstBlt(rdpContext *c, const MULTI_DSTBLT_ORDER *mdo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiPatBlt(rdpContext *c, const MULTI_PATBLT_ORDER *mpo)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiScrBlt(rdpContext *c, const MULTI_SCRBLT_ORDER *mso)
+{
+ return TRUE;
+}
+
+static BOOL
+MultiOpaqueRect(rdpContext *context, const MULTI_OPAQUE_RECT_ORDER *moro)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ uint32_t color = ConvertColor(
+ moro->color, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_ABGR32, NULL);
+ uint32_t nr = moro->numRectangles;
+ size_t buf_size
+ = (sizeof(color) + sizeof(nr)) + (sizeof(DELTA_RECT) * nr);
+ uint8_t *buf = malloc(buf_size);
+ memcpy(buf, (uint8_t *)&color, sizeof(color));
+ memcpy(buf + sizeof(color), (uint8_t *)&nr, sizeof(nr));
+ memcpy(buf + (sizeof(color) + sizeof(color)),
+ (uint8_t *)&moro->rectangles[1], sizeof(DELTA_RECT) * nr);
+ c->my_internals->core->send_multi_opaque_rect(
+ buf, buf_size, c->my_internals->task_info);
+ free(buf);
+ return TRUE;
+}
+
+static BOOL
+MultiDrawNineGrid(rdpContext *c, const MULTI_DRAW_NINE_GRID_ORDER *mdngo)
+{
+ return TRUE;
+}
+
+static BOOL
+LineTo(rdpContext *c, const LINE_TO_ORDER *lto)
+{
+ return TRUE;
+}
+
+static BOOL
+Polyline(rdpContext *c, const POLYLINE_ORDER *po)
+{
+ return TRUE;
+}
+
+static BOOL
+MemBlt(rdpContext *c, MEMBLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+Mem3Blt(rdpContext *c, MEM3BLT_ORDER *mo)
+{
+ return TRUE;
+}
+
+static BOOL
+SaveBitmap(rdpContext *c, const SAVE_BITMAP_ORDER *sbo)
+{
+ return TRUE;
+}
+
+static BOOL
+GlyphIndex(rdpContext *c, GLYPH_INDEX_ORDER *gio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastIndex(rdpContext *c, const FAST_INDEX_ORDER *fio)
+{
+ return TRUE;
+}
+
+static BOOL
+FastGlyph(rdpContext *c, const FAST_GLYPH_ORDER *fgo)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonSC(rdpContext *c, const POLYGON_SC_ORDER *pso)
+{
+ return TRUE;
+}
+
+static BOOL
+PolygonCB(rdpContext *c, POLYGON_CB_ORDER *pco)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseSC(rdpContext *c, const ELLIPSE_SC_ORDER *eso)
+{
+ return TRUE;
+}
+
+static BOOL
+EllipseCB(rdpContext *c, const ELLIPSE_CB_ORDER *eco)
+{
+ return TRUE;
+}
+void
+RegisterPrimary(freerdp *rdp)
+{
+ rdp->update->primary->DstBlt = DstBlt;
+ rdp->update->primary->PatBlt = PatBlt;
+ rdp->update->primary->ScrBlt = ScrBlt;
+ rdp->update->primary->OpaqueRect = OpaqueRect;
+ rdp->update->primary->DrawNineGrid = DrawNineGrid;
+ rdp->update->primary->MultiDstBlt = MultiDstBlt;
+ rdp->update->primary->MultiPatBlt = MultiPatBlt;
+ rdp->update->primary->MultiScrBlt = MultiScrBlt;
+ rdp->update->primary->MultiOpaqueRect = MultiOpaqueRect;
+ rdp->update->primary->MultiDrawNineGrid = MultiDrawNineGrid;
+ rdp->update->primary->LineTo = LineTo;
+ rdp->update->primary->Polyline = Polyline;
+ rdp->update->primary->MemBlt = MemBlt;
+ rdp->update->primary->Mem3Blt = Mem3Blt;
+ rdp->update->primary->SaveBitmap = SaveBitmap;
+ rdp->update->primary->GlyphIndex = GlyphIndex;
+ rdp->update->primary->FastIndex = FastIndex;
+ rdp->update->primary->FastGlyph = FastGlyph;
+ rdp->update->primary->PolygonSC = PolygonSC;
+ rdp->update->primary->PolygonCB = PolygonCB;
+ rdp->update->primary->EllipseSC = EllipseSC;
+ rdp->update->primary->EllipseCB = EllipseCB;
+}
+
+/* incomming input from client handlers */
+
+static bool
+rdp_backend_handle_input_mouse(ws_input_mouse input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ _i->core->reset_idle(_i->task_info);
+ inp->MouseEvent(inp, input.flags, input.x, input.y);
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kupdown(ws_input_kupdown input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ if (0 < input.code)
+ {
+ _i->core->reset_idle(_i->task_info);
+ /*log::info << "257 >> tcode: " << input.code << "\n"; */
+ /* make byte */
+ input.code = RDP_SCANCODE_CODE(input.code);
+ /* apply extended */
+ input.code = ASCII_TO_SCANCODE[input.code];
+ /* extract extended sepparatelly in tflag */
+ uint32_t tflag = RDP_SCANCODE_EXTENDED(input.code) ?
+ KBD_FLAGS_EXTENDED :
+ 0;
+ /* now make the call */
+ freerdp_input_send_keyboard_event(inp,
+ (input.down ? KBD_FLAGS_DOWN : KBD_FLAGS_RELEASE) | tflag,
+ input.code);
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kpress(ws_input_kpress input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ _i->core->reset_idle(_i->task_info);
+ if (0x20 < input.code)
+ {
+ if (input.shiftstate & 6)
+ {
+ //Control and or Alt: Must use scan-codes since
+ //unicode-event can't handle these
+ if (((64 < input.code) && (91 > input.code))
+ || ((96 < input.code) && (123 > input.code)))
+ {
+ input.code -= (input.shiftstate & 1) ? 0 : 32;
+ input.code
+ = GetVirtualScanCodeFromVirtualKeyCode(
+ input.code, 4);
+ if (0 < input.code)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ if (input.code == 96)
+ {
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_DELETE);
+ }
+
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ input.code = RDP_SCANCODE_CODE(input.code);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE, ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kcomb(ws_input_keycomb input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ _i->core->reset_idle(_i->task_info);
+ switch (input)
+ {
+ case ws_keycomb_ctrlaltdel_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_DELETE);
+ }
+ break;
+ case ws_keycomb_alttab_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_TAB);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_TAB);
+ }
+ break;
+ case ws_keycomb_alttab_release:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_unicode(ws_input_unicode input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->input;
+ int i;
+ _i->core->reset_idle(_i->task_info);
+ for (i = 0; i < input.length; ++i)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN, (UINT16)input.input[i]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, (UINT16)input.input[i]);
+ }
+ return true;
+}
+
+void
+register_input(wrdp_backend_module *backend)
+{
+ backend->backend_handle_input_kcomb = rdp_backend_handle_input_kcomb;
+ backend->backend_handle_input_kupdown
+ = rdp_backend_handle_input_kupdown;
+ backend->backend_handle_input_kpress = rdp_backend_handle_input_kpress;
+ backend->backend_handle_input_mouse = rdp_backend_handle_input_mouse;
+ backend->backend_handle_input_unicode
+ = rdp_backend_handle_input_unicode;
+}
+
+static BOOL
+rdp_pre_connect(freerdp *instance)
+{
+ rdpSettings *settings = instance->settings;
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+
+ RegisterUpdate(instance);
+ RegisterPrimary(instance);
+
+ /* looks like this settings reducing artifacts level */
+ settings->OrderSupport[NEG_PATBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_SCRBLT_INDEX] = FALSE;
+
+ /* causing random disconnects */
+ settings->OrderSupport[NEG_ATEXTOUT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_DRAWNINEGRID_INDEX] = FALSE;
+ settings->NoBitmapCompressionHeader = TRUE;
+ settings->BitmapCompressionDisabled = TRUE;
+ settings->FastPathOutput = FALSE;
+ settings->BitmapCacheEnabled = FALSE;
+ settings->BrushSupportLevel = 0;
+
+ /* causing connection failure on recent versions of freerdp */
+ settings->OffscreenSupportLevel = 0;
+ settings->OffscreenCacheSize = 0;
+
+ /* following required at least on windows 7 */
+ settings->OrderSupport[NEG_MEMBLT_INDEX] = FALSE;
+ settings->OrderSupport[NEG_MEM3BLT_INDEX] = FALSE;
+
+ instance->context->cache = cache_new(settings);
+
+ c->my_internals->clrconv
+ = freerdp_clrconv_new(CLRCONV_ALPHA | CLRCONV_INVERT);
+
+ if (!instance->context->cache)
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL
+rdp_post_connect(freerdp *instance)
+{
+ my_rdp_context *c = (my_rdp_context *)instance->context;
+ c->my_internals->conn_state = rdp_conn_state_connected;
+ rdpPointer p;
+ memset(&p, 0, sizeof(p));
+ p.size = sizeof(my_rdp_pointer);
+ register_pointer(&p);
+ graphics_register_pointer(instance->context->graphics, &p);
+ pointer_cache_register_callbacks(instance->update);
+
+ c->my_internals->core->send_text_msg(
+ "C:RDP session connection started.", c->my_internals->task_info);
+
+ instance->update->DesktopResize(instance->context);
+
+ return TRUE;
+}
+
+static DWORD
+rdp_verify_certificate(freerdp *instance, const char *common_name,
+ const char *subject, const char *issuer, const char *fingerprint,
+ BOOL host_mismatch)
+{
+ WLog_INFO(TAG, "Certificate details:");
+ WLog_INFO(TAG, "\tSubject: %s", subject);
+ WLog_INFO(TAG, "\tIssuer: %s", issuer);
+ WLog_INFO(TAG, "\tThumbprint: %s", fingerprint);
+ WLog_INFO(TAG,
+ "The above X.509 certificate could not be verified, possibly "
+ "because you do not have "
+ "the CA certificate in your certificate store, or the "
+ "certificate has expired. "
+ "Please look at the OpenSSL documentation on how to add a "
+ "private CA to the store.");
+
+ return TRUE; /* ?? */
+}
+
+/*static int
+rdp_receive_channel_data (freerdp* instance,
+ UINT16 channelId,
+ BYTE* data,
+ int size,
+ int flags,
+ int total_size)
+{
+ return freerdp_channels_data (
+ instance, channelId, data, size, flags, total_size);
+}*/
+
+void
+rdp_register_base(freerdp *r)
+{
+ r->PreConnect = rdp_pre_connect;
+ r->PostConnect = rdp_post_connect;
+ r->VerifyCertificate = rdp_verify_certificate;
+ // r->ReceiveChannelData = rdp_receive_channel_data;
+ r->ContextNew = rdp_context_new;
+ r->ContextFree = rdp_context_free;
+}
diff --git a/src/rdp/rdp_module.c b/src/rdp/rdp_module.c
new file mode 100644
index 0000000..a315325
--- /dev/null
+++ b/src/rdp/rdp_module.c
@@ -0,0 +1,168 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <unistd.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+
+#include <webrdp_core_api.h>
+#include <webrdp_module_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+#include "rdp_display_output.h"
+#include "rdp_user_input.h"
+#include "rdp_clipboard.h"
+#include "rdp_ft.h"
+#include "rdp_module.h"
+#include "rdp_settings.h"
+
+/* static void
+send_termination_message (rdp_internals* i)
+{
+ i->core->send_termination_msg (i->task_info);
+} */
+
+static bool
+rdp_init(void *internals)
+{
+ bool ret = true;
+ if (!rdp_init_internals(internals))
+ {
+ /* TODO: handle error */
+ return false;
+ }
+ if (!((rdp_internals *)internals)->instance)
+ {
+ /* TODO: handle error */
+ ret = false;
+ }
+ if (!rdp_init_settings(internals))
+ {
+ /* TODO: handle error */
+ ret = false;
+ }
+ if (!rdp_connect(internals))
+ {
+ /* TODO: handle error */
+ ret = false;
+ }
+ if (!ret)
+ {
+ rdp_internals *i = internals;
+ i->core->api_msgs->send_error_msg(
+ "rdp_module_internal_error", i->task_info);
+ /*send_termination_message (i);*/
+ i->core->api_core->task_finished(false, i->task_info);
+ }
+ return ret;
+}
+
+static void
+destroy_rdp(void *internals)
+{
+ rdp_internals *_i = internals;
+ /* stop all rdp fd's watchers */
+ int i;
+ for (i = 0; i < _i->rdp_fd_count; ++i)
+ {
+ if (ev_is_active(&(_i->rdp_fd[i])))
+ {
+ ev_io_stop(
+ _i->core->api_core->get_libev_loop(_i->task_info),
+ &(_i->rdp_fd[i]));
+ }
+ }
+ if (_i->conn_state == rdp_conn_state_connected)
+ {
+ freerdp_disconnect(_i->instance);
+ _i->conn_state = rdp_conn_state_offline;
+ }
+ _i->rdp_fd_count = 0;
+ if (_i->context)
+ {
+ if (_i->context->clipboard && _i->context->clipboard->srv_fmts
+ && _i->context->clipboard->srv_fmts_count)
+ {
+ for (i = 0; i < _i->context->clipboard->srv_fmts_count;
+ ++i)
+ {
+ if (_i->context->clipboard->srv_fmts[i]
+ .rdp_fmt.formatName)
+ {
+ free(_i->context->clipboard->srv_fmts[i]
+ .rdp_fmt.formatName);
+ }
+ }
+ free(_i->context->clipboard->srv_fmts);
+ }
+ if (_i->context->clipboard && _i->context->clipboard->clipboard)
+ {
+ ClipboardDestroy(_i->context->clipboard->clipboard);
+ }
+ if (_i->context->clipboard
+ && _i->context->clipboard->client_filelist_cache)
+ {
+ free(_i->context->clipboard->client_filelist_cache);
+ _i->context->clipboard->client_filelist_cache = 0;
+ }
+ free(_i->context->ft_to_client);
+ free(_i->context->ft_to_server);
+ }
+ if (_i->instance)
+ {
+ freerdp_context_free(_i->instance);
+ freerdp_free(_i->instance);
+ _i->instance = 0;
+ }
+ if (internals)
+ {
+ rdp_settings_free(internals);
+ internals = 0;
+ }
+}
+
+static void
+rdp_set_task_info(void *task_info, void *internals)
+{
+ rdp_internals *i = internals;
+ i->task_info = task_info;
+}
+
+bool
+rdp_create(wrdp_core_exports *core, wrdp_backend_module *module)
+{
+ wrdp_backend_module *backend = module;
+ if (!backend)
+ {
+ return false;
+ }
+ backend->callbacks_module->init = rdp_init;
+ backend->callbacks_module->destroy = destroy_rdp;
+ backend->callbacks_module->set_setting_int = rdp_set_setting_int;
+ backend->callbacks_module->set_setting_str = rdp_set_setting_str;
+ backend->callbacks_module->set_task_info = rdp_set_task_info;
+ register_input(backend);
+ register_clipboard(backend);
+ register_ft(backend);
+ backend->backend_internals = calloc(1, sizeof(rdp_internals));
+ if (!backend->backend_internals)
+ {
+ perror("calloc");
+ return false;
+ }
+ memset(&(((rdp_internals *)backend->backend_internals)->my_settings), 0,
+ sizeof(my_rdp_settings));
+ /* TODO: do we need to zero each option structure in settings ? */
+ {
+ rdp_internals *i = backend->backend_internals;
+ i->core = core;
+ i->backend = backend;
+ }
+
+ return true;
+}
diff --git a/src/rdp/rdp_module.h b/src/rdp/rdp_module.h
new file mode 100644
index 0000000..59842d3
--- /dev/null
+++ b/src/rdp/rdp_module.h
@@ -0,0 +1,7 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once \ No newline at end of file
diff --git a/src/rdp/rdp_png.c b/src/rdp/rdp_png.c
new file mode 100644
index 0000000..09f01a2
--- /dev/null
+++ b/src/rdp/rdp_png.c
@@ -0,0 +1,89 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include "rdp_png.h"
+#include <stdlib.h>
+#include <string.h>
+
+static void
+cbPngError(png_structp png, png_const_charp msg)
+{
+ printf("Png error: %s\n", (msg ? msg : "unknown error"));
+}
+
+static void
+cbPngWarn(png_structp png, png_const_charp msg)
+{
+ printf("Png warning: %s\n", (msg ? msg : "unknown error"));
+}
+
+static void
+cbPngWrite(png_structp png, png_bytep data, png_size_t len)
+{
+ rdp_png_buf *out_buf = (rdp_png_buf *)png_get_io_ptr(png);
+ uint8_t *ptr = out_buf->buf;
+ if (out_buf->written + len > out_buf->buf_size)
+ {
+ printf("error: rdp_png_buf is too small\n");
+ return;
+ }
+ ptr += out_buf->written;
+ memcpy(ptr, data, len);
+ out_buf->written += len;
+}
+
+static void
+cbPngFlush(png_structp png)
+{
+}
+
+bool
+png_generate_from_argb(
+ int width, int height, uint8_t *data, rdp_png_buf *out_buf)
+{
+ png_infop info_ptr;
+ png_structp png_ptr = png_create_write_struct(
+ PNG_LIBPNG_VER_STRING, 0, cbPngError, cbPngWarn);
+ if (!png_ptr)
+ {
+ printf("Could not allocate png_struct\n");
+ return false;
+ }
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ {
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+ printf("Could not allocate png_info_struct\n");
+ return false;
+ }
+ png_set_write_fn(png_ptr, out_buf, cbPngWrite, cbPngFlush);
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8,
+ PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+ png_bytep *rows = malloc(sizeof(png_bytep) * height);
+ if (!rows)
+ {
+ perror("malloc");
+ return false;
+ }
+ for (int i = 0; i < height; ++i)
+ {
+ rows[i] = (png_bytep)data;
+ data += width * 4;
+ }
+ png_set_rows(png_ptr, info_ptr, rows);
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
+
+ free(rows);
+
+ return true;
+}
+
+void
+png_destroy(png_structp *p, png_infop *i)
+{
+ png_destroy_write_struct(p, i);
+}
diff --git a/src/rdp/rdp_png.h b/src/rdp/rdp_png.h
new file mode 100644
index 0000000..a3a3bfc
--- /dev/null
+++ b/src/rdp/rdp_png.h
@@ -0,0 +1,22 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include <png.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef struct
+{
+ uint8_t *buf;
+ size_t buf_size, written;
+} rdp_png_buf;
+
+void png_destroy(png_structp *p, png_infop *i);
+
+bool png_generate_from_argb(
+ int width, int height, uint8_t *data, rdp_png_buf *out_buf);
diff --git a/src/rdp/rdp_rail.c b/src/rdp/rdp_rail.c
new file mode 100644
index 0000000..bde1950
--- /dev/null
+++ b/src/rdp/rdp_rail.c
@@ -0,0 +1,173 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+#include "rdp_backend_api.h"
+#include "rdp_rail.h"
+
+static UINT
+RailServerSystemParam(
+ RailClientContext *context, const RAIL_SYSPARAM_ORDER *sysparam)
+{
+ return CHANNEL_RC_OK;
+}
+
+static UINT
+RailServerHandshake(
+ RailClientContext *context, const RAIL_HANDSHAKE_ORDER *handshake)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+static UINT
+RailServerHandshakeEx(
+ RailClientContext *context, const RAIL_HANDSHAKE_EX_ORDER *handshakeEx)
+{
+ return client_rail_server_start_cmd(context);
+}
+
+static UINT
+RailServerLocalMoveSize(
+ RailClientContext *context, const RAIL_LOCALMOVESIZE_ORDER *localMoveSize)
+{
+ return CHANNEL_RC_OK;
+}
+static UINT
+RailServerMinMaxInfo(
+ RailClientContext *context, const RAIL_MINMAXINFO_ORDER *minMaxInfo)
+{
+ return CHANNEL_RC_OK;
+}
+
+static UINT
+RailServerLanguageBarInfo(
+ RailClientContext *context, const RAIL_LANGBAR_INFO_ORDER *langBarInfo)
+{
+ return CHANNEL_RC_OK;
+}
+static UINT
+RailServerExecuteResult(
+ RailClientContext *context, const RAIL_EXEC_RESULT_ORDER *execResult)
+{
+ my_rdp_context *c = (my_rdp_context *)context;
+ if (execResult->execResult != RAIL_EXEC_S_OK)
+ {
+ const char *msg
+ = "rdp_module: rail: RailServerExecuteResult failure";
+ c->my_internals->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ }
+ return CHANNEL_RC_OK;
+}
+
+static UINT
+RailServerGetAppIdResponse(
+ RailClientContext *context, const RAIL_GET_APPID_RESP_ORDER *getAppIdResp)
+{
+ return CHANNEL_RC_OK;
+}
+
+static BOOL
+WindowCreate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_STATE_ORDER *window_state)
+{
+ return TRUE;
+}
+static BOOL
+WindowUpdate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_STATE_ORDER *window_state)
+{
+ return TRUE;
+}
+static BOOL
+WindowIcon(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_ICON_ORDER *window_icon)
+{
+ return TRUE;
+}
+static BOOL
+WindowCachedIcon(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const WINDOW_CACHED_ICON_ORDER *window_cached_icon)
+{
+ return TRUE;
+}
+static BOOL
+WindowDelete(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo)
+{
+ return TRUE;
+}
+static BOOL
+NotifyIconCreate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const NOTIFY_ICON_STATE_ORDER *notify_icon_state)
+{
+ return TRUE;
+}
+static BOOL
+NotifyIconUpdate(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const NOTIFY_ICON_STATE_ORDER *notify_icon_state)
+{
+ return TRUE;
+}
+static BOOL
+NotifyIconDelete(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo)
+{
+ return TRUE;
+}
+static BOOL
+MonitoredDesktop(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo,
+ const MONITORED_DESKTOP_ORDER *monitored_desktop)
+{
+ return TRUE;
+}
+static BOOL
+NonMonitoredDesktop(rdpContext *context, const WINDOW_ORDER_INFO *orderInfo)
+{
+ return TRUE;
+}
+
+static void
+rdp_rail_register_update_callbacks(rdpUpdate *update)
+{
+ rdpWindowUpdate *window = update->window;
+ window->WindowCreate = WindowCreate;
+ window->WindowUpdate = WindowUpdate;
+ window->WindowDelete = WindowDelete;
+ window->WindowIcon = WindowIcon;
+ window->WindowCachedIcon = WindowCachedIcon;
+ window->NotifyIconCreate = NotifyIconCreate;
+ window->NotifyIconUpdate = NotifyIconUpdate;
+ window->NotifyIconDelete = NotifyIconDelete;
+ window->MonitoredDesktop = MonitoredDesktop;
+ window->NonMonitoredDesktop = NonMonitoredDesktop;
+}
+
+void
+rdp_rail_init(my_rdp_context *context, RailClientContext *rail)
+{
+ if (!context || !rail)
+ return;
+
+ rdp_rail_register_update_callbacks(context->context.update);
+ rail->custom = (void *)context;
+ rail->ServerExecuteResult = RailServerExecuteResult;
+ rail->ServerSystemParam = RailServerSystemParam;
+ rail->ServerHandshake = RailServerHandshake;
+ rail->ServerHandshakeEx = RailServerHandshakeEx;
+ rail->ServerLocalMoveSize = RailServerLocalMoveSize;
+ rail->ServerMinMaxInfo = RailServerMinMaxInfo;
+ rail->ServerLanguageBarInfo = RailServerLanguageBarInfo;
+ rail->ServerGetAppIdResponse = RailServerGetAppIdResponse;
+}
+
+void
+rdp_rail_uninit(my_rdp_context *context, RailClientContext *rail)
+{
+}
diff --git a/src/rdp/rdp_rail.h b/src/rdp/rdp_rail.h
new file mode 100644
index 0000000..0d3d7ba
--- /dev/null
+++ b/src/rdp/rdp_rail.h
@@ -0,0 +1,14 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+#include "rdp_channels.h"
+#include "rdp_impl.h"
+
+void rdp_rail_init(my_rdp_context *context, RailClientContext *rail);
+
+void rdp_rail_uninit(my_rdp_context *context, RailClientContext *rail);
diff --git a/src/rdp/rdp_settings.c b/src/rdp/rdp_settings.c
new file mode 100644
index 0000000..ecb1dcc
--- /dev/null
+++ b/src/rdp/rdp_settings.c
@@ -0,0 +1,2278 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <webrdp_module_api.h>
+#include <webrdp_core_api.h>
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+
+/* this variable used in integer settng name field to indicate used setting */
+static const char *setting_used = "1";
+
+bool
+rdp_set_setting_str(backend_setting_str *setting, void *internals)
+{
+ rdp_internals *i = internals;
+#ifdef DEBUG
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "rdp backend handling setting string: \"%s: %s\"",
+ setting->name, setting->value);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+#endif /* DEBUG */
+ if (!strcmp(setting->name, "host"))
+ {
+ free(i->my_settings.host.value);
+ i->my_settings.host.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "pcb"))
+ {
+ /*
+ free (i->my_settings.pcb.value);
+ i->my_settings.pcb.value = strdup (setting->value); */
+ return true;
+ }
+ else if (!strcmp(setting->name, "user") && setting->value)
+ {
+ free(i->my_settings.user.value);
+ i->my_settings.user.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "password") && setting->value)
+ {
+ free(i->my_settings.password.value);
+ i->my_settings.password.value = strdup(setting->value);
+ return true;
+ }
+
+ /* handle int settings passed as strings */
+ if (!strcmp(setting->name, "port"))
+ {
+ i->my_settings.port.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.port.value = atoll(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "width"))
+ {
+ i->my_settings.width.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.width.value = atoll(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "height"))
+ {
+ i->my_settings.height.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.height.value = atoll(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "perf"))
+ {
+ i->my_settings.perf.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.perf.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "fntlm"))
+ {
+ i->my_settings.fntlm.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.fntlm.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowallp"))
+ {
+ i->my_settings.nowallp.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nowallp.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowdrag"))
+ {
+ i->my_settings.nowdrag.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nowdrag.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nomani"))
+ {
+ i->my_settings.nomani.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nomani.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "notheme"))
+ {
+ i->my_settings.notheme.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.notheme.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "nonla"))
+ {
+ i->my_settings.nonla.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.nonla.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "notls"))
+ {
+ i->my_settings.notls.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.notls.value = atoi(setting->value);
+ }
+ return true;
+ }
+
+ /*new settings from freerdp header (strings) begin */
+ else if (!strcmp(setting->name, "Domain") && setting->value)
+ {
+ free(i->my_settings.Domain.value);
+ i->my_settings.Domain.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PasswordHash") && setting->value)
+ {
+ free(i->my_settings.PasswordHash.value);
+ i->my_settings.PasswordHash.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AcceptedCert") && setting->value)
+ {
+ free(i->my_settings.AcceptedCert.value);
+ i->my_settings.AcceptedCert.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientHostname") && setting->value)
+ {
+ free(i->my_settings.ClientHostname.value);
+ i->my_settings.ClientHostname.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientProductId") && setting->value)
+ {
+ free(i->my_settings.ClientProductId.value);
+ i->my_settings.ClientProductId.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AlternateShell") && setting->value)
+ {
+ free(i->my_settings.AlternateShell.value);
+ i->my_settings.AlternateShell.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ShellWorkingDirectory")
+ && setting->value)
+ {
+ free(i->my_settings.ShellWorkingDirectory.value);
+ i->my_settings.ShellWorkingDirectory.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientAddress") && setting->value)
+ {
+ free(i->my_settings.ClientAddress.value);
+ i->my_settings.ClientAddress.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientDir") && setting->value)
+ {
+ free(i->my_settings.ClientDir.value);
+ i->my_settings.ClientDir.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "DynamicDSTTimeZoneKeyName")
+ && setting->value)
+ {
+ free(i->my_settings.DynamicDSTTimeZoneKeyName.value);
+ i->my_settings.DynamicDSTTimeZoneKeyName.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistanceSessionId")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistanceSessionId.value);
+ i->my_settings.RemoteAssistanceSessionId.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistancePassStub")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistancePassStub.value);
+ i->my_settings.RemoteAssistancePassStub.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistancePassword")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistancePassword.value);
+ i->my_settings.RemoteAssistancePassword.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteAssistanceRCTicket")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteAssistanceRCTicket.value);
+ i->my_settings.RemoteAssistanceRCTicket.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AuthenticationServiceClass")
+ && setting->value)
+ {
+ free(i->my_settings.AuthenticationServiceClass.value);
+ i->my_settings.AuthenticationServiceClass.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowedTlsCiphers") && setting->value)
+ {
+ free(i->my_settings.AllowedTlsCiphers.value);
+ i->my_settings.AllowedTlsCiphers.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "NtlmSamFile") && setting->value)
+ {
+ free(i->my_settings.NtlmSamFile.value);
+ i->my_settings.NtlmSamFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PreconnectionBlob") && setting->value)
+ {
+ free(i->my_settings.PreconnectionBlob.value);
+ i->my_settings.PreconnectionBlob.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RedirectionAcceptedCert")
+ && setting->value)
+ {
+ free(i->my_settings.RedirectionAcceptedCert.value);
+ i->my_settings.RedirectionAcceptedCert.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "KerberosKdc") && setting->value)
+ {
+ free(i->my_settings.KerberosKdc.value);
+ i->my_settings.KerberosKdc.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "KerberosRealm") && setting->value)
+ {
+ free(i->my_settings.KerberosRealm.value);
+ i->my_settings.KerberosRealm.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CertificateName") && setting->value)
+ {
+ free(i->my_settings.CertificateName.value);
+ i->my_settings.CertificateName.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CertificateFile") && setting->value)
+ {
+ free(i->my_settings.CertificateFile.value);
+ i->my_settings.CertificateFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PrivateKeyFile") && setting->value)
+ {
+ free(i->my_settings.PrivateKeyFile.value);
+ i->my_settings.PrivateKeyFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CertificateContent") && setting->value)
+ {
+ free(i->my_settings.CertificateContent.value);
+ i->my_settings.CertificateContent.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PrivateKeyContent") && setting->value)
+ {
+ free(i->my_settings.PrivateKeyContent.value);
+ i->my_settings.PrivateKeyContent.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "WindowTitle") && setting->value)
+ {
+ free(i->my_settings.WindowTitle.value);
+ i->my_settings.WindowTitle.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "WmClass") && setting->value)
+ {
+ free(i->my_settings.WmClass.value);
+ i->my_settings.WmClass.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ComputerName") && setting->value)
+ {
+ free(i->my_settings.ComputerName.value);
+ i->my_settings.ComputerName.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConnectionFile") && setting->value)
+ {
+ free(i->my_settings.ConnectionFile.value);
+ i->my_settings.ConnectionFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "AssistanceFile") && setting->value)
+ {
+ free(i->my_settings.AssistanceFile.value);
+ i->my_settings.AssistanceFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "HomePath") && setting->value)
+ {
+ free(i->my_settings.HomePath.value);
+ i->my_settings.HomePath.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConfigPath") && setting->value)
+ {
+ free(i->my_settings.ConfigPath.value);
+ i->my_settings.ConfigPath.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "CurrentPath") && setting->value)
+ {
+ free(i->my_settings.CurrentPath.value);
+ i->my_settings.CurrentPath.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "DumpRemoteFxFile") && setting->value)
+ {
+ free(i->my_settings.DumpRemoteFxFile.value);
+ i->my_settings.DumpRemoteFxFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "PlayRemoteFxFile") && setting->value)
+ {
+ free(i->my_settings.PlayRemoteFxFile.value);
+ i->my_settings.PlayRemoteFxFile.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayHostname") && setting->value)
+ {
+ free(i->my_settings.GatewayHostname.value);
+ i->my_settings.GatewayHostname.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayUsername") && setting->value)
+ {
+ free(i->my_settings.GatewayUsername.value);
+ i->my_settings.GatewayUsername.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayPassword") && setting->value)
+ {
+ free(i->my_settings.GatewayPassword.value);
+ i->my_settings.GatewayPassword.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayDomain") && setting->value)
+ {
+ free(i->my_settings.GatewayDomain.value);
+ i->my_settings.GatewayDomain.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayAccessToken") && setting->value)
+ {
+ free(i->my_settings.GatewayAccessToken.value);
+ i->my_settings.GatewayAccessToken.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayAcceptedCert")
+ && setting->value)
+ {
+ free(i->my_settings.GatewayAcceptedCert.value);
+ i->my_settings.GatewayAcceptedCert.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ProxyHostname") && setting->value)
+ {
+ free(i->my_settings.ProxyHostname.value);
+ i->my_settings.ProxyHostname.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationName")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationName.value);
+ i->my_settings.RemoteApplicationName.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationIcon")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationIcon.value);
+ i->my_settings.RemoteApplicationIcon.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationProgram")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationProgram.value);
+ i->my_settings.RemoteApplicationProgram.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationFile")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationFile.value);
+ i->my_settings.RemoteApplicationFile.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationGuid")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationGuid.value);
+ i->my_settings.RemoteApplicationGuid.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationCmdLine")
+ && setting->value)
+ {
+ free(i->my_settings.RemoteApplicationCmdLine.value);
+ i->my_settings.RemoteApplicationCmdLine.value
+ = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ImeFileName") && setting->value)
+ {
+ free(i->my_settings.ImeFileName.value);
+ i->my_settings.ImeFileName.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "DrivesToRedirect") && setting->value)
+ {
+ free(i->my_settings.DrivesToRedirect.value);
+ i->my_settings.DrivesToRedirect.value = strdup(setting->value);
+ return true;
+ }
+ else if (!strcmp(setting->name, "ActionScript") && setting->value)
+ {
+ free(i->my_settings.ActionScript.value);
+ i->my_settings.ActionScript.value = strdup(setting->value);
+ return true;
+ }
+ /*new settings from freerdp header (strings) end */
+
+ /* TODO: handle also integer settings passed as strings */
+
+ /* new settings from freerdp header (integers as string) begin */
+
+ else if (!strcmp(setting->name, "ServerMode"))
+ {
+ i->my_settings.ServerMode.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ServerMode.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "WaitForOutputBufferFlush"))
+ {
+ i->my_settings.WaitForOutputBufferFlush.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.WaitForOutputBufferFlush.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "MaxTimeInCheckLoop"))
+ {
+ i->my_settings.MaxTimeInCheckLoop.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.MaxTimeInCheckLoop.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopWidth"))
+ {
+ i->my_settings.DesktopWidth.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopWidth.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopHeight"))
+ {
+ i->my_settings.DesktopHeight.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopHeight.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Workarea"))
+ {
+ i->my_settings.Workarea.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Workarea.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Fullscreen"))
+ {
+ i->my_settings.Fullscreen.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Fullscreen.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "GrabKeyboard"))
+ {
+ i->my_settings.GrabKeyboard.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.GrabKeyboard.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Decorations"))
+ {
+ i->my_settings.Decorations.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Decorations.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpVersion"))
+ {
+ i->my_settings.RdpVersion.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RdpVersion.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ColorDepth"))
+ {
+ i->my_settings.ColorDepth.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ColorDepth.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ExtSecurity"))
+ {
+ i->my_settings.ExtSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ExtSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "NlaSecurity"))
+ {
+ i->my_settings.NlaSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.NlaSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "TlsSecurity"))
+ {
+ i->my_settings.TlsSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.TlsSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpSecurity"))
+ {
+ i->my_settings.RdpSecurity.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RdpSecurity.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "NegotiateSecurityLayer"))
+ {
+ i->my_settings.NegotiateSecurityLayer.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.NegotiateSecurityLayer.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RestrictedAdminModeRequired"))
+ {
+ i->my_settings.RestrictedAdminModeRequired.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RestrictedAdminModeRequired.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "MstscCookieMode"))
+ {
+ i->my_settings.MstscCookieMode.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.MstscCookieMode.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "CookieMaxLength"))
+ {
+ i->my_settings.CookieMaxLength.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.CookieMaxLength.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientBuild"))
+ {
+ i->my_settings.ClientBuild.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ClientBuild.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardType"))
+ {
+ i->my_settings.KeyboardType.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardType.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardSubType"))
+ {
+ i->my_settings.KeyboardSubType.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardSubType.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardFunctionKey"))
+ {
+ i->my_settings.KeyboardFunctionKey.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardFunctionKey.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardLayout"))
+ {
+ i->my_settings.KeyboardLayout.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.KeyboardLayout.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "UseRdpSecurityLayer"))
+ {
+ i->my_settings.UseRdpSecurityLayer.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.UseRdpSecurityLayer.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "SaltedChecksum"))
+ {
+ i->my_settings.SaltedChecksum.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.SaltedChecksum.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ServerPort"))
+ {
+ i->my_settings.ServerPort.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ServerPort.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayPort"))
+ {
+ i->my_settings.GatewayPort.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.GatewayPort.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopResize"))
+ {
+ i->my_settings.DesktopResize.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopResize.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ToggleFullscreen"))
+ {
+ i->my_settings.ToggleFullscreen.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ToggleFullscreen.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "Floatbar"))
+ {
+ i->my_settings.Floatbar.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.Floatbar.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosX"))
+ {
+ i->my_settings.DesktopPosX.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopPosX.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosY"))
+ {
+ i->my_settings.DesktopPosY.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DesktopPosY.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "SoftwareGdi"))
+ {
+ i->my_settings.SoftwareGdi.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.SoftwareGdi.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "UnmapButtons"))
+ {
+ i->my_settings.UnmapButtons.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.UnmapButtons.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "PerformanceFlags"))
+ {
+ i->my_settings.PerformanceFlags.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.PerformanceFlags.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowFontSmoothing"))
+ {
+ i->my_settings.AllowFontSmoothing.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.AllowFontSmoothing.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowDesktopComposition"))
+ {
+ i->my_settings.AllowDesktopComposition.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.AllowDesktopComposition.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableWallpaper"))
+ {
+ i->my_settings.DisableWallpaper.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableWallpaper.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableFullWindowDrag"))
+ {
+ i->my_settings.DisableFullWindowDrag.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableFullWindowDrag.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableMenuAnims"))
+ {
+ i->my_settings.DisableMenuAnims.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableMenuAnims.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableThemes"))
+ {
+ i->my_settings.DisableThemes.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.DisableThemes.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConnectionType"))
+ {
+ i->my_settings.ConnectionType.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.ConnectionType.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionMethods"))
+ {
+ i->my_settings.EncryptionMethods.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.EncryptionMethods.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionLevel"))
+ {
+ i->my_settings.EncryptionLevel.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.EncryptionLevel.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "FIPSMode"))
+ {
+ i->my_settings.FIPSMode.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.FIPSMode.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "CompressionEnabled"))
+ {
+ i->my_settings.CompressionEnabled.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.CompressionEnabled.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "LogonNotify"))
+ {
+ i->my_settings.LogonNotify.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.LogonNotify.value = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "BrushSupportLevel"))
+ {
+ i->my_settings.BrushSupportLevel.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.BrushSupportLevel.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationMode"))
+ {
+ i->my_settings.RemoteApplicationMode.name
+ = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.RemoteApplicationMode.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ else if (!strcmp(setting->name, "TcpAckTimeout"))
+ {
+ i->my_settings.TcpAckTimeout.name = (char *)setting_used;
+ if (setting->value)
+ {
+ i->my_settings.TcpAckTimeout.value
+ = atoi(setting->value);
+ }
+ return true;
+ }
+ /* new settings from freerdp header (integers as string) end */
+
+ /* error: unhandled setting */
+ {
+ char buf[128];
+ snprintf(buf, 127, "rdp_module: unknown setting string: %s",
+ setting->name);
+ i->core->api_utils->log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return false;
+}
+
+bool
+rdp_set_setting_int(backend_setting_int *setting, void *internals)
+{
+ rdp_internals *i = internals;
+#ifdef DEBUG
+ {
+ char buf[128];
+ snprintf(buf, 127,
+ "rdp backend handling setting int: \"%s: %ld\"",
+ setting->name, setting->value);
+ i->core->api_utils->log_msg(
+ (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0);
+ }
+#endif
+ if (!strcmp(setting->name, "port"))
+ {
+ i->my_settings.port.name = (char *)setting_used;
+ i->my_settings.port.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "width"))
+ {
+ i->my_settings.width.name = (char *)setting_used;
+ i->my_settings.width.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "height"))
+ {
+ i->my_settings.height.name = (char *)setting_used;
+ i->my_settings.height.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "perf"))
+ {
+ i->my_settings.perf.name = (char *)setting_used;
+ i->my_settings.perf.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "fntlm"))
+ {
+ i->my_settings.fntlm.name = (char *)setting_used;
+ i->my_settings.fntlm.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowallp"))
+ {
+ i->my_settings.nowallp.name = (char *)setting_used;
+ i->my_settings.nowallp.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nowdrag"))
+ {
+ i->my_settings.nowdrag.name = (char *)setting_used;
+ i->my_settings.nowdrag.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nomani"))
+ {
+ i->my_settings.nomani.name = (char *)setting_used;
+ i->my_settings.nomani.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "notheme"))
+ {
+ i->my_settings.notheme.name = (char *)setting_used;
+ i->my_settings.notheme.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "nonla"))
+ {
+ i->my_settings.nonla.name = (char *)setting_used;
+ i->my_settings.nonla.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "notls"))
+ {
+ i->my_settings.notls.name = (char *)setting_used;
+ i->my_settings.notls.value = setting->value;
+ return true;
+ }
+
+ /* new settings from freerdp header (integers) begin */
+ else if (!strcmp(setting->name, "ServerMode"))
+ {
+ i->my_settings.ServerMode.name = (char *)setting_used;
+ i->my_settings.ServerMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "WaitForOutputBufferFlush"))
+ {
+ i->my_settings.WaitForOutputBufferFlush.name
+ = (char *)setting_used;
+ i->my_settings.WaitForOutputBufferFlush.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "MaxTimeInCheckLoop"))
+ {
+ i->my_settings.MaxTimeInCheckLoop.name = (char *)setting_used;
+ i->my_settings.MaxTimeInCheckLoop.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopWidth"))
+ {
+ i->my_settings.DesktopWidth.name = (char *)setting_used;
+ i->my_settings.DesktopWidth.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopHeight"))
+ {
+ i->my_settings.DesktopHeight.name = (char *)setting_used;
+ i->my_settings.DesktopHeight.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Workarea"))
+ {
+ i->my_settings.Workarea.name = (char *)setting_used;
+ i->my_settings.Workarea.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Fullscreen"))
+ {
+ i->my_settings.Fullscreen.name = (char *)setting_used;
+ i->my_settings.Fullscreen.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "GrabKeyboard"))
+ {
+ i->my_settings.GrabKeyboard.name = (char *)setting_used;
+ i->my_settings.GrabKeyboard.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Decorations"))
+ {
+ i->my_settings.Decorations.name = (char *)setting_used;
+ i->my_settings.Decorations.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpVersion"))
+ {
+ i->my_settings.RdpVersion.name = (char *)setting_used;
+ i->my_settings.RdpVersion.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ColorDepth"))
+ {
+ i->my_settings.ColorDepth.name = (char *)setting_used;
+ i->my_settings.ColorDepth.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ExtSecurity"))
+ {
+ i->my_settings.ExtSecurity.name = (char *)setting_used;
+ i->my_settings.ExtSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "NlaSecurity"))
+ {
+ i->my_settings.NlaSecurity.name = (char *)setting_used;
+ i->my_settings.NlaSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "TlsSecurity"))
+ {
+ i->my_settings.TlsSecurity.name = (char *)setting_used;
+ i->my_settings.TlsSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RdpSecurity"))
+ {
+ i->my_settings.RdpSecurity.name = (char *)setting_used;
+ i->my_settings.RdpSecurity.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "NegotiateSecurityLayer"))
+ {
+ i->my_settings.NegotiateSecurityLayer.name
+ = (char *)setting_used;
+ i->my_settings.NegotiateSecurityLayer.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RestrictedAdminModeRequired"))
+ {
+ i->my_settings.RestrictedAdminModeRequired.name
+ = (char *)setting_used;
+ i->my_settings.RestrictedAdminModeRequired.value
+ = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "MstscCookieMode"))
+ {
+ i->my_settings.MstscCookieMode.name = (char *)setting_used;
+ i->my_settings.MstscCookieMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "CookieMaxLength"))
+ {
+ i->my_settings.CookieMaxLength.name = (char *)setting_used;
+ i->my_settings.CookieMaxLength.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ClientBuild"))
+ {
+ i->my_settings.ClientBuild.name = (char *)setting_used;
+ i->my_settings.ClientBuild.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardType"))
+ {
+ i->my_settings.KeyboardType.name = (char *)setting_used;
+ i->my_settings.KeyboardType.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardSubType"))
+ {
+ i->my_settings.KeyboardSubType.name = (char *)setting_used;
+ i->my_settings.KeyboardSubType.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardFunctionKey"))
+ {
+ i->my_settings.KeyboardFunctionKey.name = (char *)setting_used;
+ i->my_settings.KeyboardFunctionKey.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "KeyboardLayout"))
+ {
+ i->my_settings.KeyboardLayout.name = (char *)setting_used;
+ i->my_settings.KeyboardLayout.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "UseRdpSecurityLayer"))
+ {
+ i->my_settings.UseRdpSecurityLayer.name = (char *)setting_used;
+ i->my_settings.UseRdpSecurityLayer.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "SaltedChecksum"))
+ {
+ i->my_settings.SaltedChecksum.name = (char *)setting_used;
+ i->my_settings.SaltedChecksum.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ServerPort"))
+ {
+ i->my_settings.ServerPort.name = (char *)setting_used;
+ i->my_settings.ServerPort.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "GatewayPort"))
+ {
+ i->my_settings.GatewayPort.name = (char *)setting_used;
+ i->my_settings.GatewayPort.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopResize"))
+ {
+ i->my_settings.DesktopResize.name = (char *)setting_used;
+ i->my_settings.DesktopResize.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ToggleFullscreen"))
+ {
+ i->my_settings.ToggleFullscreen.name = (char *)setting_used;
+ i->my_settings.ToggleFullscreen.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "Floatbar"))
+ {
+ i->my_settings.Floatbar.name = (char *)setting_used;
+ i->my_settings.Floatbar.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosX"))
+ {
+ i->my_settings.DesktopPosX.name = (char *)setting_used;
+ i->my_settings.DesktopPosX.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DesktopPosY"))
+ {
+ i->my_settings.DesktopPosY.name = (char *)setting_used;
+ i->my_settings.DesktopPosY.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "SoftwareGdi"))
+ {
+ i->my_settings.SoftwareGdi.name = (char *)setting_used;
+ i->my_settings.SoftwareGdi.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "UnmapButtons"))
+ {
+ i->my_settings.UnmapButtons.name = (char *)setting_used;
+ i->my_settings.UnmapButtons.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "PerformanceFlags"))
+ {
+ i->my_settings.PerformanceFlags.name = (char *)setting_used;
+ i->my_settings.PerformanceFlags.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowFontSmoothing"))
+ {
+ i->my_settings.AllowFontSmoothing.name = (char *)setting_used;
+ i->my_settings.AllowFontSmoothing.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "AllowDesktopComposition"))
+ {
+ i->my_settings.AllowDesktopComposition.name
+ = (char *)setting_used;
+ i->my_settings.AllowDesktopComposition.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableWallpaper"))
+ {
+ i->my_settings.DisableWallpaper.name = (char *)setting_used;
+ i->my_settings.DisableWallpaper.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableFullWindowDrag"))
+ {
+ i->my_settings.DisableFullWindowDrag.name
+ = (char *)setting_used;
+ i->my_settings.DisableFullWindowDrag.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableMenuAnims"))
+ {
+ i->my_settings.DisableMenuAnims.name = (char *)setting_used;
+ i->my_settings.DisableMenuAnims.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "DisableThemes"))
+ {
+ i->my_settings.DisableThemes.name = (char *)setting_used;
+ i->my_settings.DisableThemes.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "ConnectionType"))
+ {
+ i->my_settings.ConnectionType.name = (char *)setting_used;
+ i->my_settings.ConnectionType.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionMethods"))
+ {
+ i->my_settings.EncryptionMethods.name = (char *)setting_used;
+ i->my_settings.EncryptionMethods.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "EncryptionLevel"))
+ {
+ i->my_settings.EncryptionLevel.name = (char *)setting_used;
+ i->my_settings.EncryptionLevel.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "FIPSMode"))
+ {
+ i->my_settings.FIPSMode.name = (char *)setting_used;
+ i->my_settings.FIPSMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "CompressionEnabled"))
+ {
+ i->my_settings.CompressionEnabled.name = (char *)setting_used;
+ i->my_settings.CompressionEnabled.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "LogonNotify"))
+ {
+ i->my_settings.LogonNotify.name = (char *)setting_used;
+ i->my_settings.LogonNotify.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "BrushSupportLevel"))
+ {
+ i->my_settings.BrushSupportLevel.name = (char *)setting_used;
+ i->my_settings.BrushSupportLevel.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "RemoteApplicationMode"))
+ {
+ i->my_settings.RemoteApplicationMode.name
+ = (char *)setting_used;
+ i->my_settings.RemoteApplicationMode.value = setting->value;
+ return true;
+ }
+ else if (!strcmp(setting->name, "TcpAckTimeout"))
+ {
+ i->my_settings.TcpAckTimeout.name = (char *)setting_used;
+ i->my_settings.TcpAckTimeout.value = setting->value;
+ return true;
+ }
+
+ /* new settings from freerdp header (integers) end */
+
+ /* error: unhandled setting */
+ {
+ char buf[128];
+ snprintf(buf, 127, "rdp_module: unknown setting int: %s",
+ setting->name);
+ i->core->api_utils->log_msg((const uint8_t *)buf, strlen(buf),
+ wrdp_log_level_warning, 0);
+ }
+ return false;
+}
+
+void
+rdp_settings_free(rdp_internals *internals)
+{
+ rdp_internals *i = internals;
+ if (i->my_settings.host.value)
+ {
+ free((void *)i->my_settings.host.value);
+ }
+ if (i->my_settings.pcb.value)
+ {
+ free((void *)i->my_settings.pcb.value);
+ }
+ if (i->my_settings.user.value)
+ {
+ free((void *)i->my_settings.user.value);
+ }
+ if (i->my_settings.password.value)
+ {
+ free((void *)i->my_settings.password.value);
+ }
+ /* new */
+ if (i->my_settings.Domain.value)
+ {
+ free((void *)i->my_settings.Domain.value);
+ }
+ if (i->my_settings.PasswordHash.value)
+ {
+ free((void *)i->my_settings.PasswordHash.value);
+ }
+ if (i->my_settings.AcceptedCert.value)
+ {
+ free((void *)i->my_settings.AcceptedCert.value);
+ }
+ if (i->my_settings.ClientHostname.value)
+ {
+ free((void *)i->my_settings.ClientHostname.value);
+ }
+ if (i->my_settings.ClientProductId.value)
+ {
+ free((void *)i->my_settings.ClientProductId.value);
+ }
+ if (i->my_settings.AlternateShell.value)
+ {
+ free((void *)i->my_settings.AlternateShell.value);
+ }
+ if (i->my_settings.ShellWorkingDirectory.value)
+ {
+ free((void *)i->my_settings.ShellWorkingDirectory.value);
+ }
+ if (i->my_settings.ClientAddress.value)
+ {
+ free((void *)i->my_settings.ClientAddress.value);
+ }
+ if (i->my_settings.ClientDir.value)
+ {
+ free((void *)i->my_settings.ClientDir.value);
+ }
+ if (i->my_settings.DynamicDSTTimeZoneKeyName.value)
+ {
+ free((void *)i->my_settings.DynamicDSTTimeZoneKeyName.value);
+ }
+ if (i->my_settings.RemoteAssistanceSessionId.value)
+ {
+ free((void *)i->my_settings.RemoteAssistanceSessionId.value);
+ }
+ if (i->my_settings.RemoteAssistancePassStub.value)
+ {
+ free((void *)i->my_settings.RemoteAssistancePassStub.value);
+ }
+ if (i->my_settings.RemoteAssistancePassword.value)
+ {
+ free((void *)i->my_settings.RemoteAssistancePassword.value);
+ }
+ if (i->my_settings.RemoteAssistanceRCTicket.value)
+ {
+ free((void *)i->my_settings.RemoteAssistanceRCTicket.value);
+ }
+ if (i->my_settings.AuthenticationServiceClass.value)
+ {
+ free((void *)i->my_settings.AuthenticationServiceClass.value);
+ }
+ if (i->my_settings.AllowedTlsCiphers.value)
+ {
+ free((void *)i->my_settings.AllowedTlsCiphers.value);
+ }
+ if (i->my_settings.NtlmSamFile.value)
+ {
+ free((void *)i->my_settings.NtlmSamFile.value);
+ }
+ if (i->my_settings.PreconnectionBlob.value)
+ {
+ free((void *)i->my_settings.PreconnectionBlob.value);
+ }
+ if (i->my_settings.RedirectionAcceptedCert.value)
+ {
+ free((void *)i->my_settings.RedirectionAcceptedCert.value);
+ }
+ if (i->my_settings.KerberosKdc.value)
+ {
+ free((void *)i->my_settings.KerberosKdc.value);
+ }
+ if (i->my_settings.KerberosRealm.value)
+ {
+ free((void *)i->my_settings.KerberosRealm.value);
+ }
+ if (i->my_settings.CertificateName.value)
+ {
+ free((void *)i->my_settings.CertificateName.value);
+ }
+ if (i->my_settings.CertificateFile.value)
+ {
+ free((void *)i->my_settings.CertificateFile.value);
+ }
+ if (i->my_settings.PrivateKeyFile.value)
+ {
+ free((void *)i->my_settings.PrivateKeyFile.value);
+ }
+ if (i->my_settings.CertificateContent.value)
+ {
+ free((void *)i->my_settings.CertificateContent.value);
+ }
+ if (i->my_settings.PrivateKeyContent.value)
+ {
+ free((void *)i->my_settings.PrivateKeyContent.value);
+ }
+ if (i->my_settings.WindowTitle.value)
+ {
+ free((void *)i->my_settings.WindowTitle.value);
+ }
+ if (i->my_settings.WmClass.value)
+ {
+ free((void *)i->my_settings.WmClass.value);
+ }
+ if (i->my_settings.ComputerName.value)
+ {
+ free((void *)i->my_settings.ComputerName.value);
+ }
+ if (i->my_settings.ConnectionFile.value)
+ {
+ free((void *)i->my_settings.ConnectionFile.value);
+ }
+ if (i->my_settings.AssistanceFile.value)
+ {
+ free((void *)i->my_settings.AssistanceFile.value);
+ }
+ if (i->my_settings.HomePath.value)
+ {
+ free((void *)i->my_settings.HomePath.value);
+ }
+ if (i->my_settings.ConfigPath.value)
+ {
+ free((void *)i->my_settings.ConfigPath.value);
+ }
+ if (i->my_settings.CurrentPath.value)
+ {
+ free((void *)i->my_settings.CurrentPath.value);
+ }
+ if (i->my_settings.DumpRemoteFxFile.value)
+ {
+ free((void *)i->my_settings.DumpRemoteFxFile.value);
+ }
+ if (i->my_settings.PlayRemoteFxFile.value)
+ {
+ free((void *)i->my_settings.PlayRemoteFxFile.value);
+ }
+ if (i->my_settings.GatewayHostname.value)
+ {
+ free((void *)i->my_settings.GatewayHostname.value);
+ }
+ if (i->my_settings.GatewayUsername.value)
+ {
+ free((void *)i->my_settings.GatewayUsername.value);
+ }
+ if (i->my_settings.GatewayPassword.value)
+ {
+ free((void *)i->my_settings.GatewayPassword.value);
+ }
+ if (i->my_settings.GatewayDomain.value)
+ {
+ free((void *)i->my_settings.GatewayDomain.value);
+ }
+ if (i->my_settings.GatewayAccessToken.value)
+ {
+ free((void *)i->my_settings.GatewayAccessToken.value);
+ }
+ if (i->my_settings.GatewayAcceptedCert.value)
+ {
+ free((void *)i->my_settings.GatewayAcceptedCert.value);
+ }
+ if (i->my_settings.ProxyHostname.value)
+ {
+ free((void *)i->my_settings.ProxyHostname.value);
+ }
+ if (i->my_settings.RemoteApplicationName.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationName.value);
+ }
+ if (i->my_settings.RemoteApplicationIcon.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationIcon.value);
+ }
+ if (i->my_settings.RemoteApplicationProgram.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationProgram.value);
+ }
+ if (i->my_settings.RemoteApplicationFile.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationFile.value);
+ }
+ if (i->my_settings.RemoteApplicationGuid.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationGuid.value);
+ }
+ if (i->my_settings.RemoteApplicationCmdLine.value)
+ {
+ free((void *)i->my_settings.RemoteApplicationCmdLine.value);
+ }
+ if (i->my_settings.ImeFileName.value)
+ {
+ free((void *)i->my_settings.ImeFileName.value);
+ }
+ if (i->my_settings.DrivesToRedirect.value)
+ {
+ free((void *)i->my_settings.DrivesToRedirect.value);
+ }
+ if (i->my_settings.ActionScript.value)
+ {
+ free((void *)i->my_settings.ActionScript.value);
+ }
+}
+
+bool
+rdp_init_settings(void *internals)
+{
+ rdp_internals *i = internals;
+ rdpSettings *s = i->instance->context->settings;
+
+ /* via are not server here ) */
+ s->ServerMode = FALSE;
+
+ s->TlsSecurity = !(i->my_settings.notls.value);
+ if (i->my_settings.host.value && i->my_settings.host.value[0])
+ {
+ s->ServerHostname = strdup((char *)i->my_settings.host.value);
+ }
+ else
+ {
+ const char *msg = "rdp_module: server hostname does not set.";
+ i->core->api_utils->log_msg(
+ (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0);
+ return FALSE;
+ }
+ if (i->my_settings.port.name && i->my_settings.port.name[0] == '1')
+ {
+ s->ServerPort = i->my_settings.port.value;
+ }
+ if (i->my_settings.user.value && i->my_settings.user.value[0])
+ {
+ s->Username = strdup((char *)i->my_settings.user.value);
+ }
+ if (i->my_settings.password.value && i->my_settings.password.value[0])
+ {
+ s->Password = strdup((char *)i->my_settings.password.value);
+ }
+ if (!i->my_settings.user.value && !i->my_settings.password.value)
+ {
+ /* TODO: disable authentication */
+ }
+ if (i->my_settings.nowallp.name
+ && i->my_settings.nowallp.name[0] == '1')
+ {
+ s->DisableWallpaper = i->my_settings.nowallp.value;
+ s->PerformanceFlags |= PERF_DISABLE_WALLPAPER;
+ }
+ if (i->my_settings.nowdrag.name
+ && i->my_settings.nowdrag.name[0] == '1')
+ {
+ s->DisableFullWindowDrag = i->my_settings.nowdrag.value;
+ s->PerformanceFlags |= PERF_DISABLE_FULLWINDOWDRAG;
+ }
+ if (i->my_settings.nomani.name && i->my_settings.nomani.name[0] == '1')
+ {
+ s->DisableMenuAnims = i->my_settings.nomani.value;
+ s->PerformanceFlags |= PERF_DISABLE_MENUANIMATIONS;
+ }
+ if (i->my_settings.notheme.name
+ && i->my_settings.notheme.name[0] == '1')
+ {
+ s->DisableThemes = i->my_settings.notheme.value;
+ s->PerformanceFlags |= PERF_DISABLE_THEMING;
+ }
+ if (i->my_settings.nonla.name && i->my_settings.nonla.name[0] == '1')
+ {
+ s->NlaSecurity = !(i->my_settings.nonla.value);
+ }
+ if (i->my_settings.width.name && i->my_settings.width.name[0] == '1')
+ {
+ s->DesktopWidth = i->my_settings.width.value;
+ }
+ if (i->my_settings.height.name && i->my_settings.height.name[0] == '1')
+ {
+ s->DesktopHeight = i->my_settings.height.value;
+ }
+ if (i->my_settings.pcb.value && i->my_settings.pcb.value[0])
+ {
+ s->SendPreconnectionPdu = TRUE;
+ s->PreconnectionBlob = strdup(i->my_settings.pcb.value);
+ }
+
+ s->IgnoreCertificate = TRUE;
+
+ if (i->my_settings.perf.name && i->my_settings.perf.name[0] == '1')
+ {
+ switch (i->my_settings.perf.value)
+ {
+ case 0:
+ //LAN
+ s->PerformanceFlags = PERF_FLAG_NONE;
+ s->ConnectionType = CONNECTION_TYPE_LAN;
+ s->AllowFontSmoothing = TRUE;
+ break;
+ case 1:
+ //Broadband
+ s->PerformanceFlags = PERF_DISABLE_WALLPAPER;
+ s->ConnectionType
+ = CONNECTION_TYPE_BROADBAND_HIGH;
+ break;
+ case 2:
+ //Modem
+ s->PerformanceFlags
+ = PERF_DISABLE_WALLPAPER
+ | PERF_DISABLE_FULLWINDOWDRAG
+ | PERF_DISABLE_MENUANIMATIONS
+ | PERF_DISABLE_THEMING;
+ s->ConnectionType = CONNECTION_TYPE_MODEM;
+ break;
+ }
+ }
+
+ /* new settings from freerdp header (strings) begin */
+
+ if (i->my_settings.Domain.value)
+ {
+ s->Domain = strdup(i->my_settings.Domain.value);
+ }
+ if (i->my_settings.PasswordHash.value)
+ {
+ s->PasswordHash = strdup(i->my_settings.PasswordHash.value);
+ }
+ if (i->my_settings.AcceptedCert.value)
+ {
+ s->AcceptedCert = strdup(i->my_settings.AcceptedCert.value);
+ }
+ if (i->my_settings.ClientHostname.value)
+ {
+ s->ClientHostname = strdup(i->my_settings.ClientHostname.value);
+ }
+ if (i->my_settings.ClientProductId.value)
+ {
+ s->ClientProductId
+ = strdup(i->my_settings.ClientProductId.value);
+ }
+ if (i->my_settings.AlternateShell.value)
+ {
+ s->AlternateShell = strdup(i->my_settings.AlternateShell.value);
+ }
+ if (i->my_settings.ShellWorkingDirectory.value)
+ {
+ s->ShellWorkingDirectory
+ = strdup(i->my_settings.ShellWorkingDirectory.value);
+ }
+ if (i->my_settings.ClientAddress.value)
+ {
+ s->ClientAddress = strdup(i->my_settings.ClientAddress.value);
+ }
+ if (i->my_settings.ClientDir.value)
+ {
+ s->ClientDir = strdup(i->my_settings.ClientDir.value);
+ }
+ if (i->my_settings.DynamicDSTTimeZoneKeyName.value)
+ {
+ s->DynamicDSTTimeZoneKeyName
+ = strdup(i->my_settings.DynamicDSTTimeZoneKeyName.value);
+ }
+ if (i->my_settings.RemoteAssistanceSessionId.value)
+ {
+ s->RemoteAssistanceSessionId
+ = strdup(i->my_settings.RemoteAssistanceSessionId.value);
+ }
+ if (i->my_settings.RemoteAssistancePassStub.value)
+ {
+ s->RemoteAssistancePassStub
+ = strdup(i->my_settings.RemoteAssistancePassStub.value);
+ }
+ if (i->my_settings.RemoteAssistancePassword.value)
+ {
+ s->RemoteAssistancePassword
+ = strdup(i->my_settings.RemoteAssistancePassword.value);
+ }
+ if (i->my_settings.RemoteAssistanceRCTicket.value)
+ {
+ s->RemoteAssistanceRCTicket
+ = strdup(i->my_settings.RemoteAssistanceRCTicket.value);
+ }
+ if (i->my_settings.AuthenticationServiceClass.value)
+ {
+ s->AuthenticationServiceClass
+ = strdup(i->my_settings.AuthenticationServiceClass.value);
+ }
+ if (i->my_settings.AllowedTlsCiphers.value)
+ {
+ s->AllowedTlsCiphers
+ = strdup(i->my_settings.AllowedTlsCiphers.value);
+ }
+ if (i->my_settings.NtlmSamFile.value)
+ {
+ s->NtlmSamFile = strdup(i->my_settings.NtlmSamFile.value);
+ }
+ if (i->my_settings.PreconnectionBlob.value)
+ {
+ s->PreconnectionBlob
+ = strdup(i->my_settings.PreconnectionBlob.value);
+ }
+ if (i->my_settings.RedirectionAcceptedCert.value)
+ {
+ s->RedirectionAcceptedCert
+ = strdup(i->my_settings.RedirectionAcceptedCert.value);
+ }
+ if (i->my_settings.KerberosKdc.value)
+ {
+ s->KerberosKdc = strdup(i->my_settings.KerberosKdc.value);
+ }
+ if (i->my_settings.KerberosRealm.value)
+ {
+ s->KerberosRealm = strdup(i->my_settings.KerberosRealm.value);
+ }
+ if (i->my_settings.CertificateName.value)
+ {
+ s->CertificateName
+ = strdup(i->my_settings.CertificateName.value);
+ }
+ if (i->my_settings.CertificateFile.value)
+ {
+ s->CertificateFile
+ = strdup(i->my_settings.CertificateFile.value);
+ }
+ if (i->my_settings.PrivateKeyFile.value)
+ {
+ s->PrivateKeyFile = strdup(i->my_settings.PrivateKeyFile.value);
+ }
+ if (i->my_settings.CertificateContent.value)
+ {
+ s->CertificateContent
+ = strdup(i->my_settings.CertificateContent.value);
+ }
+ if (i->my_settings.PrivateKeyContent.value)
+ {
+ s->PrivateKeyContent
+ = strdup(i->my_settings.PrivateKeyContent.value);
+ }
+ if (i->my_settings.WindowTitle.value)
+ {
+ s->WindowTitle = strdup(i->my_settings.WindowTitle.value);
+ }
+ if (i->my_settings.WmClass.value)
+ {
+ s->WmClass = strdup(i->my_settings.WmClass.value);
+ }
+ if (i->my_settings.ComputerName.value)
+ {
+ s->ComputerName = strdup(i->my_settings.ComputerName.value);
+ }
+ if (i->my_settings.ConnectionFile.value)
+ {
+ s->ConnectionFile = strdup(i->my_settings.ConnectionFile.value);
+ }
+ if (i->my_settings.AssistanceFile.value)
+ {
+ s->AssistanceFile = strdup(i->my_settings.AssistanceFile.value);
+ }
+ if (i->my_settings.HomePath.value)
+ {
+ s->HomePath = strdup(i->my_settings.HomePath.value);
+ }
+ if (i->my_settings.ConfigPath.value)
+ {
+ s->ConfigPath = strdup(i->my_settings.ConfigPath.value);
+ }
+ if (i->my_settings.CurrentPath.value)
+ {
+ s->CurrentPath = strdup(i->my_settings.CurrentPath.value);
+ }
+ if (i->my_settings.DumpRemoteFxFile.value)
+ {
+ s->DumpRemoteFxFile
+ = strdup(i->my_settings.DumpRemoteFxFile.value);
+ }
+ if (i->my_settings.PlayRemoteFxFile.value)
+ {
+ s->PlayRemoteFxFile
+ = strdup(i->my_settings.PlayRemoteFxFile.value);
+ }
+ if (i->my_settings.GatewayHostname.value)
+ {
+ s->GatewayHostname
+ = strdup(i->my_settings.GatewayHostname.value);
+ }
+ if (i->my_settings.GatewayUsername.value)
+ {
+ s->GatewayUsername
+ = strdup(i->my_settings.GatewayUsername.value);
+ }
+ if (i->my_settings.GatewayPassword.value)
+ {
+ s->GatewayPassword
+ = strdup(i->my_settings.GatewayPassword.value);
+ }
+ if (i->my_settings.GatewayDomain.value)
+ {
+ s->GatewayDomain = strdup(i->my_settings.GatewayDomain.value);
+ }
+ if (i->my_settings.GatewayAccessToken.value)
+ {
+ s->GatewayAccessToken
+ = strdup(i->my_settings.GatewayAccessToken.value);
+ }
+ if (i->my_settings.GatewayAcceptedCert.value)
+ {
+ s->GatewayAcceptedCert
+ = strdup(i->my_settings.GatewayAcceptedCert.value);
+ }
+ if (i->my_settings.ProxyHostname.value)
+ {
+ s->ProxyHostname = strdup(i->my_settings.ProxyHostname.value);
+ }
+ if (i->my_settings.RemoteApplicationName.value)
+ {
+ s->RemoteApplicationName
+ = strdup(i->my_settings.RemoteApplicationName.value);
+ }
+ if (i->my_settings.RemoteApplicationIcon.value)
+ {
+ s->RemoteApplicationIcon
+ = strdup(i->my_settings.RemoteApplicationIcon.value);
+ }
+ if (i->my_settings.RemoteApplicationProgram.value)
+ {
+ s->RemoteApplicationProgram
+ = strdup(i->my_settings.RemoteApplicationProgram.value);
+ }
+ if (i->my_settings.RemoteApplicationFile.value)
+ {
+ s->RemoteApplicationFile
+ = strdup(i->my_settings.RemoteApplicationFile.value);
+ }
+ if (i->my_settings.RemoteApplicationGuid.value)
+ {
+ s->RemoteApplicationGuid
+ = strdup(i->my_settings.RemoteApplicationGuid.value);
+ }
+ if (i->my_settings.RemoteApplicationCmdLine.value)
+ {
+ s->RemoteApplicationCmdLine
+ = strdup(i->my_settings.RemoteApplicationCmdLine.value);
+ }
+ if (i->my_settings.ImeFileName.value)
+ {
+ s->ImeFileName = strdup(i->my_settings.ImeFileName.value);
+ }
+ if (i->my_settings.DrivesToRedirect.value)
+ {
+ s->DrivesToRedirect
+ = strdup(i->my_settings.DrivesToRedirect.value);
+ }
+ if (i->my_settings.ActionScript.value)
+ {
+ s->ActionScript = strdup(i->my_settings.ActionScript.value);
+ }
+
+ /* new settings from freerdp header (strings) end */
+
+ /* new settings from freerdp header (integers) begin */
+
+ if (i->my_settings.ServerMode.name
+ && i->my_settings.ServerMode.name[0] == '1')
+ {
+ s->ServerMode = i->my_settings.ServerMode.value;
+ }
+ if (i->my_settings.WaitForOutputBufferFlush.name
+ && i->my_settings.WaitForOutputBufferFlush.name[0] == '1')
+ {
+ s->WaitForOutputBufferFlush
+ = i->my_settings.WaitForOutputBufferFlush.value;
+ }
+ if (i->my_settings.MaxTimeInCheckLoop.name
+ && i->my_settings.MaxTimeInCheckLoop.name[0] == '1')
+ {
+ s->MaxTimeInCheckLoop = i->my_settings.MaxTimeInCheckLoop.value;
+ }
+ if (i->my_settings.DesktopWidth.name
+ && i->my_settings.DesktopWidth.name[0] == '1')
+ {
+ s->DesktopWidth = i->my_settings.DesktopWidth.value;
+ }
+ if (i->my_settings.Workarea.name
+ && i->my_settings.Workarea.name[0] == '1')
+ {
+ s->Workarea = i->my_settings.Workarea.value;
+ }
+ if (i->my_settings.Fullscreen.name
+ && i->my_settings.Fullscreen.name[0] == '1')
+ {
+ s->Fullscreen = i->my_settings.Fullscreen.value;
+ }
+ if (i->my_settings.GrabKeyboard.name
+ && i->my_settings.GrabKeyboard.name[0] == '1')
+ {
+ s->GrabKeyboard = i->my_settings.GrabKeyboard.value;
+ }
+ if (i->my_settings.Decorations.name
+ && i->my_settings.Decorations.name[0] == '1')
+ {
+ s->Decorations = i->my_settings.Decorations.value;
+ }
+ if (i->my_settings.RdpVersion.name
+ && i->my_settings.RdpVersion.name[0] == '1')
+ {
+ s->RdpVersion = i->my_settings.RdpVersion.value;
+ }
+ if (i->my_settings.ColorDepth.name
+ && i->my_settings.ColorDepth.name[0] == '1')
+ {
+ s->ColorDepth = i->my_settings.ColorDepth.value;
+ }
+ if (i->my_settings.ExtSecurity.name
+ && i->my_settings.ExtSecurity.name[0] == '1')
+ {
+ s->ExtSecurity = i->my_settings.ExtSecurity.value;
+ }
+ if (i->my_settings.NlaSecurity.name
+ && i->my_settings.NlaSecurity.name[0] == '1')
+ {
+ s->NlaSecurity = i->my_settings.NlaSecurity.value;
+ }
+ if (i->my_settings.TlsSecurity.name
+ && i->my_settings.TlsSecurity.name[0] == '1')
+ {
+ s->TlsSecurity = i->my_settings.TlsSecurity.value;
+ }
+ if (i->my_settings.RdpSecurity.name
+ && i->my_settings.RdpSecurity.name[0] == '1')
+ {
+ s->RdpSecurity = i->my_settings.RdpSecurity.value;
+ }
+ if (i->my_settings.NegotiateSecurityLayer.name
+ && i->my_settings.NegotiateSecurityLayer.name[0] == '1')
+ {
+ s->NegotiateSecurityLayer
+ = i->my_settings.NegotiateSecurityLayer.value;
+ }
+ if (i->my_settings.RestrictedAdminModeRequired.name
+ && i->my_settings.RestrictedAdminModeRequired.name[0] == '1')
+ {
+ s->RestrictedAdminModeRequired
+ = i->my_settings.RestrictedAdminModeRequired.value;
+ }
+ if (i->my_settings.MstscCookieMode.name
+ && i->my_settings.MstscCookieMode.name[0] == '1')
+ {
+ s->MstscCookieMode = i->my_settings.MstscCookieMode.value;
+ }
+ if (i->my_settings.CookieMaxLength.name
+ && i->my_settings.CookieMaxLength.name[0] == '1')
+ {
+ s->CookieMaxLength = i->my_settings.CookieMaxLength.value;
+ }
+ if (i->my_settings.ClientBuild.name
+ && i->my_settings.ClientBuild.name[0] == '1')
+ {
+ s->ClientBuild = i->my_settings.ClientBuild.value;
+ }
+ if (i->my_settings.KeyboardType.name
+ && i->my_settings.KeyboardType.name[0] == '1')
+ {
+ s->KeyboardType = i->my_settings.KeyboardType.value;
+ }
+ if (i->my_settings.KeyboardSubType.name
+ && i->my_settings.KeyboardSubType.name[0] == '1')
+ {
+ s->KeyboardSubType = i->my_settings.KeyboardSubType.value;
+ }
+ if (i->my_settings.KeyboardFunctionKey.name
+ && i->my_settings.KeyboardFunctionKey.name[0] == '1')
+ {
+ s->KeyboardFunctionKey
+ = i->my_settings.KeyboardFunctionKey.value;
+ }
+ if (i->my_settings.KeyboardLayout.name
+ && i->my_settings.KeyboardLayout.name[0] == '1')
+ {
+ s->KeyboardLayout = i->my_settings.KeyboardLayout.value;
+ }
+ if (i->my_settings.UseRdpSecurityLayer.name
+ && i->my_settings.UseRdpSecurityLayer.name[0] == '1')
+ {
+ s->UseRdpSecurityLayer
+ = i->my_settings.UseRdpSecurityLayer.value;
+ }
+ if (i->my_settings.SaltedChecksum.name
+ && i->my_settings.SaltedChecksum.name[0] == '1')
+ {
+ s->SaltedChecksum = i->my_settings.SaltedChecksum.value;
+ }
+ if (i->my_settings.ServerPort.name
+ && i->my_settings.ServerPort.name[0] == '1')
+ {
+ s->ServerPort = i->my_settings.ServerPort.value;
+ }
+ if (i->my_settings.GatewayPort.name
+ && i->my_settings.GatewayPort.name[0] == '1')
+ {
+ s->GatewayPort = i->my_settings.GatewayPort.value;
+ }
+ if (i->my_settings.DesktopResize.name
+ && i->my_settings.DesktopResize.name[0] == '1')
+ {
+ s->DesktopResize = i->my_settings.DesktopResize.value;
+ }
+ if (i->my_settings.ToggleFullscreen.name
+ && i->my_settings.ToggleFullscreen.name[0] == '1')
+ {
+ s->ToggleFullscreen = i->my_settings.ToggleFullscreen.value;
+ }
+ if (i->my_settings.Floatbar.name
+ && i->my_settings.Floatbar.name[0] == '1')
+ {
+ s->Floatbar = i->my_settings.Floatbar.value;
+ }
+ if (i->my_settings.DesktopPosX.name
+ && i->my_settings.DesktopPosX.name[0] == '1')
+ {
+ s->DesktopPosX = i->my_settings.DesktopPosX.value;
+ }
+ if (i->my_settings.DesktopPosY.name
+ && i->my_settings.DesktopPosY.name[0] == '1')
+ {
+ s->DesktopPosY = i->my_settings.DesktopPosY.value;
+ }
+ if (i->my_settings.SoftwareGdi.name
+ && i->my_settings.SoftwareGdi.name[0] == '1')
+ {
+ s->SoftwareGdi = i->my_settings.SoftwareGdi.value;
+ }
+ if (i->my_settings.UnmapButtons.name
+ && i->my_settings.UnmapButtons.name[0] == '1')
+ {
+ s->UnmapButtons = i->my_settings.UnmapButtons.value;
+ }
+ if (i->my_settings.PerformanceFlags.name
+ && i->my_settings.PerformanceFlags.name[0] == '1')
+ {
+ s->PerformanceFlags = i->my_settings.PerformanceFlags.value;
+ }
+ if (i->my_settings.AllowFontSmoothing.name
+ && i->my_settings.AllowFontSmoothing.name[0] == '1')
+ {
+ s->AllowFontSmoothing = i->my_settings.AllowFontSmoothing.value;
+ }
+ if (i->my_settings.AllowDesktopComposition.name
+ && i->my_settings.AllowDesktopComposition.name[0] == '1')
+ {
+ s->AllowDesktopComposition
+ = i->my_settings.AllowDesktopComposition.value;
+ }
+ if (i->my_settings.DisableWallpaper.name
+ && i->my_settings.DisableWallpaper.name[0] == '1')
+ {
+ s->DisableWallpaper = i->my_settings.DisableWallpaper.value;
+ }
+ if (i->my_settings.DisableFullWindowDrag.name
+ && i->my_settings.DisableFullWindowDrag.name[0] == '1')
+ {
+ s->DisableFullWindowDrag
+ = i->my_settings.DisableFullWindowDrag.value;
+ }
+ if (i->my_settings.DisableMenuAnims.name
+ && i->my_settings.DisableMenuAnims.name[0] == '1')
+ {
+ s->DisableMenuAnims = i->my_settings.DisableMenuAnims.value;
+ }
+ if (i->my_settings.DisableThemes.name
+ && i->my_settings.DisableThemes.name[0] == '1')
+ {
+ s->DisableThemes = i->my_settings.DisableThemes.value;
+ }
+ if (i->my_settings.ConnectionType.name
+ && i->my_settings.ConnectionType.name[0] == '1')
+ {
+ s->ConnectionType = i->my_settings.ConnectionType.value;
+ }
+ if (i->my_settings.EncryptionMethods.name
+ && i->my_settings.EncryptionMethods.name[0] == '1')
+ {
+ s->EncryptionMethods = i->my_settings.EncryptionMethods.value;
+ }
+ if (i->my_settings.EncryptionLevel.name
+ && i->my_settings.EncryptionLevel.name[0] == '1')
+ {
+ s->EncryptionLevel = i->my_settings.EncryptionLevel.value;
+ }
+ if (i->my_settings.FIPSMode.name
+ && i->my_settings.FIPSMode.name[0] == '1')
+ {
+ s->FIPSMode = i->my_settings.FIPSMode.value;
+ }
+ if (i->my_settings.CompressionEnabled.name
+ && i->my_settings.CompressionEnabled.name[0] == '1')
+ {
+ s->CompressionEnabled = i->my_settings.CompressionEnabled.value;
+ }
+ if (i->my_settings.LogonNotify.name
+ && i->my_settings.LogonNotify.name[0] == '1')
+ {
+ s->LogonNotify = i->my_settings.LogonNotify.value;
+ }
+ if (i->my_settings.BrushSupportLevel.name
+ && i->my_settings.BrushSupportLevel.name[0] == '1')
+ {
+ s->BrushSupportLevel = i->my_settings.BrushSupportLevel.value;
+ }
+ if (i->my_settings.RemoteApplicationMode.name
+ && i->my_settings.RemoteApplicationMode.name[0] == '1')
+ {
+ s->RemoteApplicationMode
+ = i->my_settings.RemoteApplicationMode.value;
+ }
+ if (i->my_settings.TcpAckTimeout.name
+ && i->my_settings.TcpAckTimeout.name[0] == '1')
+ {
+ s->TcpAckTimeout = i->my_settings.TcpAckTimeout.value;
+ }
+ /* new settings from freerdp header (integers) end */
+
+ /* TODO: handle rest of options:
+ * fntlm
+ */
+
+ return true;
+}
diff --git a/src/rdp/rdp_settings.h b/src/rdp/rdp_settings.h
new file mode 100644
index 0000000..3fca325
--- /dev/null
+++ b/src/rdp/rdp_settings.h
@@ -0,0 +1,15 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+bool rdp_set_setting_str(backend_setting_str *setting, void *internals);
+
+bool rdp_set_setting_int(backend_setting_int *setting, void *internals);
+
+void rdp_settings_free(rdp_internals *internals);
+
+bool rdp_init_settings(void *internals);
diff --git a/src/rdp/rdp_user_input.c b/src/rdp/rdp_user_input.c
new file mode 100644
index 0000000..c79e5b1
--- /dev/null
+++ b/src/rdp/rdp_user_input.c
@@ -0,0 +1,458 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "webrdp_core_api.h"
+#include "webrdp_module_api.h"
+
+#include "rdp_backend_api.h"
+#include "rdp_impl.h"
+
+const UINT32 ASCII_TO_SCANCODE[256] = {
+ 0, /* 0 */
+ 0, /* 1 */
+ 0, /* 2 */
+ 0, /* 3 */
+ 0, /* 4 */
+ 0, /* 5 */
+ 0, /* 6 */
+ 0, /* 7 */
+ RDP_SCANCODE_BACKSPACE, /* 8 */
+ RDP_SCANCODE_TAB, /* 9 */
+ VK_KEY_D, /* 10 */
+ VK_KEY_F, /* 11 */
+ VK_KEY_H, /* 12 */
+ RDP_SCANCODE_RETURN, /* 13 */
+ RDP_SCANCODE_BACKSPACE, /* 14 */
+ VK_KEY_X, /* 15 */
+ RDP_SCANCODE_LSHIFT, /* 16 */
+ RDP_SCANCODE_LCONTROL, /* 17 */
+ RDP_SCANCODE_LMENU, /* 18 */
+ RDP_SCANCODE_PAUSE, /* 19 */
+ RDP_SCANCODE_CAPSLOCK, /* 20 */
+ VK_KEY_W, /* 21 */
+ VK_KEY_E, /* 22 */
+ VK_KEY_R, /* 23 */
+ VK_KEY_Y, /* 24 */
+ VK_KEY_A, /* 25 */
+ VK_KEY_1, /* 26 */
+ RDP_SCANCODE_ESCAPE, /* 27 */
+ VK_KEY_3, /* 28 */
+ VK_KEY_4, /* 29 */
+ VK_KEY_6, /* 30 */
+ VK_KEY_5, /* 31 */
+ RDP_SCANCODE_SPACE, /* 32 */
+ RDP_SCANCODE_PRIOR, /* 33 */
+ RDP_SCANCODE_NEXT, /* 34 */
+ RDP_SCANCODE_END, /* 35 */
+ RDP_SCANCODE_HOME, /* 36 */
+ RDP_SCANCODE_LEFT, /* 37 */
+ RDP_SCANCODE_UP, /* 38 */
+ RDP_SCANCODE_RIGHT, /* 39 */
+ RDP_SCANCODE_DOWN, /* 40 */
+ RDP_SCANCODE_KEY_0, /* 41 */
+ RDP_SCANCODE_MULTIPLY, /* 42 */
+ RDP_SCANCODE_ADD, /* 43 */
+ RDP_SCANCODE_PRINTSCREEN, /* 44 */
+ RDP_SCANCODE_INSERT, /* 45 */
+ RDP_SCANCODE_DELETE, /* 46 */
+ RDP_SCANCODE_DIVIDE, /* 47 */
+ RDP_SCANCODE_KEY_0, /* 48 */
+ RDP_SCANCODE_KEY_1, /* 49 */
+ RDP_SCANCODE_KEY_2, /* 50 */
+ RDP_SCANCODE_KEY_3, /* 51 */
+ RDP_SCANCODE_KEY_4, /* 52 */
+ RDP_SCANCODE_KEY_5, /* 53 */
+ RDP_SCANCODE_KEY_6, /* 54 */
+ RDP_SCANCODE_KEY_7, /* 55 */
+ RDP_SCANCODE_KEY_8, /* 56 */
+ RDP_SCANCODE_KEY_9, /* 57 */
+ RDP_SCANCODE_OEM_1, /* 58 */
+ RDP_SCANCODE_OEM_1, /* 59 */
+ RDP_SCANCODE_OEM_COMMA, /* 60 */
+ RDP_SCANCODE_OEM_PLUS, /* 61 */
+ RDP_SCANCODE_OEM_PERIOD, /* 62 */
+ RDP_SCANCODE_DIVIDE, /* 63 */
+ RDP_SCANCODE_KEY_2, /* 64 */
+ RDP_SCANCODE_KEY_A, /* 65 */
+ RDP_SCANCODE_KEY_B, /* 66 */
+ RDP_SCANCODE_KEY_C, /* 67 */
+ RDP_SCANCODE_KEY_D, /* 68 */
+ RDP_SCANCODE_KEY_E, /* 69 */
+ RDP_SCANCODE_KEY_F, /* 70 */
+ RDP_SCANCODE_KEY_G, /* 71 */
+ RDP_SCANCODE_KEY_H, /* 72 */
+ RDP_SCANCODE_KEY_I, /* 73 */
+ RDP_SCANCODE_KEY_J, /* 74 */
+ RDP_SCANCODE_KEY_K, /* 75 */
+ RDP_SCANCODE_KEY_L, /* 76 */
+ RDP_SCANCODE_KEY_M, /* 77 */
+ RDP_SCANCODE_KEY_N, /* 78 */
+ RDP_SCANCODE_KEY_O, /* 79 */
+ RDP_SCANCODE_KEY_P, /* 80 */
+ RDP_SCANCODE_KEY_Q, /* 81 */
+ RDP_SCANCODE_KEY_R, /* 82 */
+ RDP_SCANCODE_KEY_S, /* 83 */
+ RDP_SCANCODE_KEY_T, /* 84 */
+ RDP_SCANCODE_KEY_U, /* 85 */
+ RDP_SCANCODE_KEY_V, /* 86 */
+ RDP_SCANCODE_KEY_W, /* 87 */
+ RDP_SCANCODE_KEY_X, /* 88 */
+ RDP_SCANCODE_KEY_Y, /* 89 */
+ RDP_SCANCODE_KEY_Z, /* 90 */
+ RDP_SCANCODE_LWIN, /* 91 */
+ RDP_SCANCODE_RWIN, /* 92 */
+ RDP_SCANCODE_APPS, /* 93 */
+ RDP_SCANCODE_KEY_6, /* 94 */
+ RDP_SCANCODE_OEM_MINUS, /* 95 */
+ RDP_SCANCODE_NUMPAD0, /* 96 */
+ RDP_SCANCODE_NUMPAD1, /* 97 */
+ RDP_SCANCODE_NUMPAD2, /* 98 */
+ RDP_SCANCODE_NUMPAD3, /* 99 */
+ RDP_SCANCODE_NUMPAD4, /* 100 */
+ RDP_SCANCODE_NUMPAD5, /* 101 */
+ RDP_SCANCODE_NUMPAD6, /* 102 */
+ RDP_SCANCODE_NUMPAD7, /* 103 */
+ RDP_SCANCODE_NUMPAD8, /* 104 */
+ RDP_SCANCODE_NUMPAD9, /* 105 */
+ RDP_SCANCODE_MULTIPLY, /* 106 */
+ RDP_SCANCODE_ADD, /* 107 */
+ 0, /* 108 */
+ RDP_SCANCODE_SUBTRACT, /* 109 */
+ RDP_SCANCODE_DELETE, /* 110 */
+ RDP_SCANCODE_DIVIDE, /* 111 */
+ RDP_SCANCODE_F1, /* 112 */
+ RDP_SCANCODE_F2, /* 113 */
+ RDP_SCANCODE_F3, /* 114 */
+ RDP_SCANCODE_F4, /* 115 */
+ RDP_SCANCODE_F5, /* 116 */
+ RDP_SCANCODE_F6, /* 117 */
+ RDP_SCANCODE_F7, /* 118 */
+ RDP_SCANCODE_F8, /* 119 */
+ RDP_SCANCODE_F9, /* 120 */
+ RDP_SCANCODE_F10, /* 121 */
+ RDP_SCANCODE_F11, /* 122 */
+ RDP_SCANCODE_F12, /* 123 */
+ RDP_SCANCODE_OEM_5, /* 124 */
+ RDP_SCANCODE_OEM_6, /* 125 */
+ VK_F4, /* 126 */
+ VK_END, /* 127 */
+ VK_F2, /* 128 */
+ VK_NEXT, /* 129 */
+ VK_F1, /* 130 */
+ VK_LEFT, /* 131 */
+ VK_RIGHT, /* 132 */
+ VK_DOWN, /* 133 */
+ VK_UP, /* 134 */
+ 0, /* 135 */
+ 0, /* 136 */
+ 0, /* 137 */
+ 0, /* 138 */
+ 0, /* 139 */
+ 0, /* 140 */
+ 0, /* 141 */
+ 0, /* 142 */
+ 0, /* 143 */
+ RDP_SCANCODE_NUMLOCK, /* 144 */
+ RDP_SCANCODE_SCROLLLOCK, /* 145 */
+ 0, /* 146 */
+ 0, /* 147 */
+ 0, /* 148 */
+ 0, /* 149 */
+ 0, /* 150 */
+ 0, /* 151 */
+ 0, /* 152 */
+ 0, /* 153 */
+ 0, /* 154 */
+ 0, /* 155 */
+ 0, /* 156 */
+ 0, /* 157 */
+ 0, /* 158 */
+ 0, /* 159 */
+ 0, /* 160 */
+ 0, /* 161 */
+ 0, /* 162 */
+ 0, /* 163 */
+ 0, /* 164 */
+ 0, /* 165 */
+ 0, /* 166 */
+ 0, /* 167 */
+ 0, /* 168 */
+ 0, /* 169 */
+ 0, /* 170 */
+ 0, /* 171 */
+ 0, /* 172 */
+ RDP_SCANCODE_OEM_MINUS, /* 173 */
+ 0, /* 174 */
+ 0, /* 175 */
+ 0, /* 176 */
+ 0, /* 177 */
+ 0, /* 178 */
+ 0, /* 179 */
+ 0, /* 180 */
+ 0, /* 181 */
+ 0, /* 182 */
+ 0, /* 183 */
+ 0, /* 184 */
+ 0, /* 185 */
+ RDP_SCANCODE_OEM_1, /* 186 */
+ RDP_SCANCODE_OEM_PLUS, /* 187 */
+ RDP_SCANCODE_OEM_COMMA, /* 188 */
+ RDP_SCANCODE_OEM_MINUS, /* 189 */
+ RDP_SCANCODE_OEM_PERIOD, /* 190 */
+ RDP_SCANCODE_OEM_2, /* 191 */
+ RDP_SCANCODE_OEM_3, /* 192 */
+ 0, /* 193 */
+ 0, /* 194 */
+ 0, /* 195 */
+ 0, /* 196 */
+ 0, /* 197 */
+ 0, /* 198 */
+ 0, /* 199 */
+ 0, /* 200 */
+ 0, /* 201 */
+ 0, /* 202 */
+ 0, /* 203 */
+ 0, /* 204 */
+ 0, /* 205 */
+ 0, /* 206 */
+ 0, /* 207 */
+ 0, /* 208 */
+ 0, /* 209 */
+ 0, /* 210 */
+ 0, /* 211 */
+ 0, /* 212 */
+ 0, /* 213 */
+ 0, /* 214 */
+ 0, /* 215 */
+ 0, /* 216 */
+ 0, /* 217 */
+ 0, /* 218 */
+ RDP_SCANCODE_OEM_4, /* 219 */
+ RDP_SCANCODE_OEM_5, /* 220 */
+ RDP_SCANCODE_OEM_6, /* 221 */
+ RDP_SCANCODE_OEM_7, /* 222 */
+ 0, /* 223 */
+ 0, /* 224 */
+ 0, /* 225 */
+ 0, /* 226 */
+ 0, /* 227 */
+ 0, /* 228 */
+ 0, /* 229 */
+ 0, /* 230 */
+ 0, /* 231 */
+ 0, /* 232 */
+ 0, /* 233 */
+ 0, /* 234 */
+ 0, /* 235 */
+ 0, /* 236 */
+ 0, /* 237 */
+ 0, /* 238 */
+ 0, /* 239 */
+ 0, /* 240 */
+ 0, /* 241 */
+ 0, /* 242 */
+ 0, /* 243 */
+ 0, /* 244 */
+ 0, /* 245 */
+ 0, /* 246 */
+ 0, /* 247 */
+ 0, /* 248 */
+ 0, /* 249 */
+ 0, /* 250 */
+ 0, /* 251 */
+ 0, /* 252 */
+ 0, /* 253 */
+ 0, /* 254 */
+ 0 /* 255 */
+};
+
+static bool
+rdp_backend_handle_input_mouse(ws_input_mouse input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ _i->core->api_core->reset_idle(_i->task_info);
+ inp->MouseEvent(inp, input.flags, input.x, input.y);
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kupdown(ws_input_kupdown input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ if (0 < input.code)
+ {
+ _i->core->api_core->reset_idle(_i->task_info);
+ /* make byte */
+ input.code = RDP_SCANCODE_CODE(input.code);
+ /* apply extended */
+ input.code = ASCII_TO_SCANCODE[input.code];
+ /* extract extended sepparatelly in tflag */
+ uint32_t tflag = RDP_SCANCODE_EXTENDED(input.code) ?
+ KBD_FLAGS_EXTENDED :
+ 0;
+ freerdp_input_send_keyboard_event(inp,
+ (input.down ? KBD_FLAGS_DOWN : KBD_FLAGS_RELEASE) | tflag,
+ input.code);
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kpress(ws_input_kpress input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ _i->core->api_core->reset_idle(_i->task_info);
+ if (0x20 < input.code)
+ {
+ if (input.shiftstate & 6)
+ {
+ //Control and or Alt: Must use scan-codes since
+ //unicode-event can't handle these
+ if (((64 < input.code) && (91 > input.code))
+ || ((96 < input.code) && (123 > input.code)))
+ {
+ input.code -= (input.shiftstate & 1) ? 0 : 32;
+ input.code
+ = GetVirtualScanCodeFromVirtualKeyCode(
+ input.code, 4);
+ if (0 < input.code)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ if (input.code == 96)
+ {
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ (UINT8)RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ (UINT8)RDP_SCANCODE_DELETE);
+ }
+
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_DOWN,
+ ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE,
+ ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ }
+ else
+ {
+ if (0 < input.code)
+ {
+ input.code = RDP_SCANCODE_CODE(input.code);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, ASCII_TO_SCANCODE[input.code]);
+ freerdp_input_send_keyboard_event(inp,
+ KBD_FLAGS_RELEASE, ASCII_TO_SCANCODE[input.code]);
+ }
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_kcomb(ws_input_keycomb input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ _i->core->api_core->reset_idle(_i->task_info);
+ switch (input)
+ {
+ case ws_keycomb_ctrlaltdel_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, (UINT8)RDP_SCANCODE_DELETE);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LCONTROL);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, (UINT8)RDP_SCANCODE_DELETE);
+ }
+ break;
+ case ws_keycomb_alttab_press:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_LMENU);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_DOWN, RDP_SCANCODE_TAB);
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_TAB);
+ }
+ break;
+ case ws_keycomb_alttab_release:
+ {
+ freerdp_input_send_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, RDP_SCANCODE_LMENU);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool
+rdp_backend_handle_input_unicode(ws_input_unicode input, void *internals)
+{
+ rdp_internals *_i = internals;
+ rdpInput *inp = _i->instance->context->input;
+ int i;
+ _i->core->api_core->reset_idle(_i->task_info);
+ for (i = 0; i < input.length; ++i)
+ {
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_DOWN, (UINT16)input.input[i]);
+ freerdp_input_send_unicode_keyboard_event(
+ inp, KBD_FLAGS_RELEASE, (UINT16)input.input[i]);
+ }
+ return true;
+}
+
+void
+register_input(wrdp_backend_module *backend)
+{
+ backend->callbacks_input->kcomb = rdp_backend_handle_input_kcomb;
+ backend->callbacks_input->kupdown = rdp_backend_handle_input_kupdown;
+ backend->callbacks_input->kpress = rdp_backend_handle_input_kpress;
+ backend->callbacks_input->mouse = rdp_backend_handle_input_mouse;
+ backend->callbacks_input->unicode = rdp_backend_handle_input_unicode;
+}
diff --git a/src/rdp/rdp_user_input.h b/src/rdp/rdp_user_input.h
new file mode 100644
index 0000000..c7851e4
--- /dev/null
+++ b/src/rdp/rdp_user_input.h
@@ -0,0 +1,10 @@
+/* BSD-2-Clause license
+ *
+ * Copyright (c) 2018-2023 NST <www.newinfosec.ru>, sss <sss at dark-alexandr dot net>.
+ *
+ */
+
+#pragma once
+
+void register_input(wrdp_backend_module *backend);
+void register_pointer(rdpPointer *p);
diff --git a/src/tools/keygen.c b/src/tools/keygen.c
new file mode 100644
index 0000000..8e12f15
--- /dev/null
+++ b/src/tools/keygen.c
@@ -0,0 +1,57 @@
+/*-
+ * Copyright (c) 2022 Alexandr Gluzskiy <sss@sss.chaoslab.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "../core/base64_url.h"
+
+const size_t key_size = 64;
+
+int
+main()
+{
+ uint32_t buf[key_size / 4];
+ char out_buf[key_size * 3];
+ size_t out_len = 0;
+ srand((unsigned int)buf);
+ //TODO: use srand
+ // size_t pos;
+ for (size_t pos = 0; pos < key_size / 4; ++pos)
+ {
+ buf[pos] = rand();
+ }
+ base64_url_encode(
+ (uint8_t *)buf, key_size, out_buf, key_size * 3, &out_len);
+ out_buf[out_len] = 0;
+
+ printf("%s\n", out_buf);
+ return 0;
+}