diff --git a/src/config_parser.c b/src/config_parser.c index 450bc156..d3f93833 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -73,6 +73,24 @@ static int parse_unsigned(const char* text, int base, uint64_t maximum, uint64_t *out = value; return 0; } +/* Полное десятичное int без усечения и переполнения; границы задаёт владелец поля. */ +static int parse_integer(const char* text, int minimum, int maximum, int* out) { + char* end; + errno = 0; + long value = strtol(text, &end, 10); + if ((!isdigit((unsigned char)*text) && *text != '-') || end == text || *end || errno || value < minimum || value > maximum) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid integer '%s', expected %d..%d", text, minimum, maximum); return -1; + } + *out = (int)value; return 0; +} + +/* Единые булевы значения INI: 0/1, no/yes, false/true; -1 означает ошибку. */ +static int parse_boolean(const char* text) { + if (!strcmp(text, "1") || !strcasecmp(text, "yes") || !strcasecmp(text, "true")) return 1; + if (!strcmp(text, "0") || !strcasecmp(text, "no") || !strcasecmp(text, "false")) return 0; + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid boolean '%s', expected yes/no, true/false or 1/0", text); return -1; +} + /* Версия содержит ровно три десятичных октета, без хвоста; выход применяется целиком. */ static int parse_version(const char* value, uint8_t out[3]) { char text[12]; @@ -342,136 +360,75 @@ static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const } static int parse_global(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) { - if (strcmp(key, "my_node_name") == 0) { - size_t nlen = strlen(value); - if (nlen >= sizeof(global->name)) { - DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: my_node_name too long (%zu chars), truncating to %zu", - filename, line_num, nlen, sizeof(global->name) - 1); - } - strncpy(global->name, value, sizeof(global->name) - 1); - global->name[sizeof(global->name) - 1] = '\0'; - return 0; + if (!strcmp(key, "my_node_name")) { + if (strlen(value) >= sizeof(global->name)) + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: my_node_name truncated to %zu bytes", filename, line_num, sizeof(global->name) - 1); + snprintf(global->name, sizeof(global->name), "%s", value); return 0; } - if (strcmp(key, "my_private_key") == 0) { - return assign_string(global->my_private_key_hex, MAX_KEY_LEN, value); + struct { const char* name; char* field; size_t size; } strings[] = { + {"my_private_key", global->my_private_key_hex, sizeof(global->my_private_key_hex)}, + {"my_public_key", global->my_public_key_hex, sizeof(global->my_public_key_hex)}, + {"tun_ifname", global->tun_ifname, sizeof(global->tun_ifname)}, + {"debug_level", global->debug_level, sizeof(global->debug_level)}, + {"log_file", global->log_file, sizeof(global->log_file)}, + {"db_path", global->db_path, sizeof(global->db_path)} + }; + for (size_t i = 0; i < sizeof(strings) / sizeof(*strings); i++) + if (!strcmp(key, strings[i].name)) return assign_string(strings[i].field, strings[i].size, value); + struct { const char* name; int* field; int minimum, maximum; } numbers[] = { + {"mtu", &global->mtu, 0, UINT16_MAX}, + {"keepalive_timeout", &global->keepalive_timeout, 0, INT_MAX / 10}, + {"keepalive_interval", &global->keepalive_interval, 0, UINT16_MAX}, + {"bbr_max_cwnd", &global->bbr_max_cwnd, INFLIGHT_LIM_MIN, INT_MAX} + }; + for (size_t i = 0; i < sizeof(numbers) / sizeof(*numbers); i++) + if (!strcmp(key, numbers[i].name)) return parse_integer(value, numbers[i].minimum, numbers[i].maximum, numbers[i].field); + struct { const char* name; int* field; } booleans[] = { + {"tun_enabled", &global->tun_enabled}, {"keepalive_adaptive", &global->keepalive_adaptive}, + {"auto_socket_skip_no_default_route", &global->auto_socket_skip_no_default_route}, + {"db_sync_enabled", &global->db_sync_enabled}, {"chatserver_enabled", &global->chatserver_enabled}, + {"enable_timestamp", &global->enable_timestamp}, {"enable_function_names", &global->enable_function_names}, + {"enable_file_lines", &global->enable_file_lines}, {"enable_colors", &global->enable_colors}, + {"tun_test_mode", &global->tun_test_mode} + }; + for (size_t i = 0; i < sizeof(booleans) / sizeof(*booleans); i++) { + if (strcmp(key, booleans[i].name)) continue; + int parsed = parse_boolean(value); + if (parsed < 0) return -1; + *booleans[i].field = parsed; return 0; } - if (strcmp(key, "my_public_key") == 0) { - return assign_string(global->my_public_key_hex, MAX_KEY_LEN, value); + if (!strcmp(key, "my_node_id")) return parse_unsigned(value, 16, UINT64_MAX, &global->my_node_id); + if (!strcmp(key, "db_sync_ttl")) { + uint64_t parsed; + if (parse_unsigned(value, 10, UINT32_MAX, &parsed)) return -1; + global->db_sync_ttl = (uint32_t)parsed; return 0; } - if (strcmp(key, "my_node_id") == 0) { - global->my_node_id = strtoull(value, NULL, 16); - return 0; + if (!strcmp(key, "client_type")) { + int parsed; + if (parse_integer(value, 0, 2, &parsed)) return -1; + global->client_type = (uint8_t)parsed; return 0; } - if (strcmp(key, "client_type") == 0) { - global->client_type = (uint8_t)atoi(value); - if (global->client_type > 2) global->client_type = 0; - return 0; + if (!strcmp(key, "tun_ip")) { + uint8_t prefix; + return parse_ip_with_netmask(value, &global->tun_ip, &prefix); } - if (strcmp(key, "tun_enabled") == 0) { - global->tun_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; - return 0; - } - if (strcmp(key, "tun_ifname") == 0) { - snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value); - return 0; - } - if (strcmp(key, "tun_ip") == 0) { - uint8_t netmask; - return parse_ip_with_netmask(value, &global->tun_ip, &netmask); - } - if (strcmp(key, "mtu") == 0) { - global->mtu = atoi(value); - return 0; - } - if (strcmp(key, "keepalive_timeout") == 0) { - global->keepalive_timeout = atoi(value); - return 0; - } - if (strcmp(key, "keepalive_interval") == 0) { - global->keepalive_interval = atoi(value); - return 0; - } - if (strcmp(key, "keepalive_adaptive") == 0) { - global->keepalive_adaptive = atoi(value); - return 0; - } - if (strcmp(key, "bbr_max_cwnd") == 0) { - char* end; - errno = 0; - long parsed = strtol(value, &end, 10); - if (errno || end == value || *end || parsed < INFLIGHT_LIM_MIN || parsed > INT_MAX) { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: invalid bbr_max_cwnd '%s'; expected %u..%d bytes", - filename, line_num, value, INFLIGHT_LIM_MIN, INT_MAX); - return -1; - } - global->bbr_max_cwnd = (int)parsed; - return 0; - } - if (strcmp(key, "auto_sockets") == 0) { - if (strcasecmp(value, "android") == 0) global->auto_sockets = CFG_AUTO_SOCKETS_ANDROID; - else if (strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0) global->auto_sockets = CFG_AUTO_SOCKETS_SYSTEM; - else global->auto_sockets = CFG_AUTO_SOCKETS_OFF; - return 0; - } - if (strcmp(key, "auto_socket_skip_no_default_route") == 0) { - global->auto_socket_skip_no_default_route = strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0; - return 0; - } - if (strcmp(key, "auto_socket_protocols") == 0) { - if (strcasecmp(value, "tcp") == 0) global->auto_socket_protocols = CFG_AUTO_SOCKET_TCP; - else if (strcasecmp(value, "udp") == 0) global->auto_socket_protocols = CFG_AUTO_SOCKET_UDP; - else if (strcasecmp(value, "tcp+udp") == 0) global->auto_socket_protocols = CFG_AUTO_SOCKET_BOTH; + if (!strcmp(key, "auto_sockets")) { + if (!strcasecmp(value, "android")) global->auto_sockets = CFG_AUTO_SOCKETS_ANDROID; else { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: invalid auto_socket_protocols='%s'; expected tcp, udp or tcp+udp", - filename, line_num, value); - return -1; + int parsed = parse_boolean(value); + if (parsed < 0) return -1; + global->auto_sockets = parsed ? CFG_AUTO_SOCKETS_SYSTEM : CFG_AUTO_SOCKETS_OFF; } return 0; } - if (strcmp(key, "debug_level") == 0) { - return assign_string(global->debug_level, sizeof(global->debug_level), value); - } - if (strcmp(key, "log_file") == 0) { - return assign_string(global->log_file, sizeof(global->log_file), value); - } - if (strcmp(key, "db_path") == 0) { - return assign_string(global->db_path, sizeof(global->db_path), value); - } - if (strcmp(key, "db_sync_enabled") == 0) { - global->db_sync_enabled = atoi(value); + if (!strcmp(key, "auto_socket_protocols")) { + if (!strcasecmp(value, "tcp")) global->auto_socket_protocols = CFG_AUTO_SOCKET_TCP; + else if (!strcasecmp(value, "udp")) global->auto_socket_protocols = CFG_AUTO_SOCKET_UDP; + else if (!strcasecmp(value, "tcp+udp")) global->auto_socket_protocols = CFG_AUTO_SOCKET_BOTH; + else { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: expected auto_socket_protocols=tcp, udp or tcp+udp", filename, line_num); return -1; } return 0; } - if (strcmp(key, "chatserver_enabled") == 0) { - global->chatserver_enabled = atoi(value); - return 0; - } - if (strcmp(key, "db_sync_ttl") == 0) { - global->db_sync_ttl = (uint32_t)atoi(value); - return 0; - } - /* debug_categories key is deprecated - use [debug] section instead */ - if (strcmp(key, "enable_timestamp") == 0) { - global->enable_timestamp = atoi(value); - return 0; - } - if (strcmp(key, "enable_function_names") == 0) { - global->enable_function_names = atoi(value); - return 0; - } - if (strcmp(key, "enable_file_lines") == 0) { - global->enable_file_lines = atoi(value); - return 0; - } - if (strcmp(key, "enable_colors") == 0) { - global->enable_colors = atoi(value); - return 0; - } - if (strcmp(key, "tun_test_mode") == 0) { - global->tun_test_mode = atoi(value); - return 0; - } - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, client_type, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, keepalive_adaptive, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key); - return -1; + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: unknown global option '%s'", filename, line_num, key); return -1; } static int parse_control(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {