/* BSD-2-Clause license * * Copyright (c) 2018-2023 NST , sss . * */ #include #include #include #include #include #include #include #include #include #include //json.h lib #include #include "json_helpers.h" #include #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); }