diff options
author | sss <sss@dark-alexandr.net> | 2023-01-17 00:38:19 +0300 |
---|---|---|
committer | sss <sss@dark-alexandr.net> | 2023-01-17 00:38:19 +0300 |
commit | cc3f33db7a8d3c4ad373e646b199808e01bc5d9b (patch) | |
tree | ec09d690c7656ab5f2cc72607e05fb359c24d8b2 /src |
added webrdp public code
Diffstat (limited to 'src')
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="<Use Defaults>" DbgSetName="<Use Defaults>"> + <![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="<Use Defaults>" DbgSetName="<Use Defaults>"> + <![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="<Use Defaults>" DbgSetName="<Use Defaults>"> + <![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="<Use Defaults>" DbgSetName="<Use Defaults>"> + <![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; +} |