summaryrefslogtreecommitdiff
path: root/src/core/ws_protocol.c
diff options
context:
space:
mode:
authorsss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
committersss <sss@dark-alexandr.net>2023-01-17 00:38:19 +0300
commitcc3f33db7a8d3c4ad373e646b199808e01bc5d9b (patch)
treeec09d690c7656ab5f2cc72607e05fb359c24d8b2 /src/core/ws_protocol.c
added webrdp public code
Diffstat (limited to 'src/core/ws_protocol.c')
-rw-r--r--src/core/ws_protocol.c1206
1 files changed, 1206 insertions, 0 deletions
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;
+}