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/core |
added webrdp public code
Diffstat (limited to 'src/core')
47 files changed, 8947 insertions, 0 deletions
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); |