Validate global scalar options before narrowing or timer conversion

This commit is contained in:
evgeny committed 2026-10-26 09:08:23 +01:00
1 parent 4abeafa20e
commit 99d52afd3f
1 file changed
+76 -119
+76 -119
View File
@@ -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) {