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/ws_protocol.c |
added webrdp public code
Diffstat (limited to 'src/core/ws_protocol.c')
-rw-r--r-- | src/core/ws_protocol.c | 1206 |
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; +} |