Validate global scalar options before narrowing or timer conversion
This commit is contained in:
1 parent
4abeafa20e
commit
99d52afd3f
1 file changed
+76
-119
+76
-119
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user