Share INI parsing and validate complete configuration values
This commit is contained in:
1 parent
5a57b59b50
commit
2410c39b71
2 files changed
+180
-401
No files matched your search
@@ -0,0 +1,142 @@
|
||||
/* Внутренний разбор INI для config_parser и config_updater: одинаковые пробелы,
|
||||
* секции, key=value и строгий HEX. Буфер принадлежит вызывающему; trim/key_value
|
||||
* меняют его на месте. read выделяет текст через u_malloc; освобождение — u_free.
|
||||
* Строка содержит не более 1022 байтов до LF, встроенный NUL запрещён. */
|
||||
#ifndef CONFIG_INI_H
|
||||
#define CONFIG_INI_H
|
||||
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/strbuf.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CONFIG_INI_LINE_SIZE 1024
|
||||
|
||||
/* Удалить пробелы с обеих сторон, сохранив исходный адрес строки. */
|
||||
static inline char *config_ini_trim(char *text) {
|
||||
if (!text) return NULL;
|
||||
char *start = text;
|
||||
while (isspace((unsigned char)*start)) start++;
|
||||
size_t length = strlen(start);
|
||||
while (length && isspace((unsigned char)start[length - 1])) length--;
|
||||
memmove(text, start, length);
|
||||
text[length] = '\0';
|
||||
return text;
|
||||
}
|
||||
|
||||
/* Полный заголовок [name], без префиксного сопоставления; 0/-1. */
|
||||
static inline int config_ini_section(const char *line, char *name, size_t capacity) {
|
||||
size_t length = strlen(line);
|
||||
if (length < 2 || line[0] != '[' || line[length - 1] != ']' || length - 2 >= capacity) return -1;
|
||||
memcpy(name, line + 1, length - 2);
|
||||
name[length - 2] = '\0';
|
||||
config_ini_trim(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Разобрать key=value, обрезать пробелы и комментарий # в значении; 0/-1. */
|
||||
static inline int config_ini_key_value(const char *line, char *key, size_t key_cap, char *value, size_t value_cap) {
|
||||
const char *equal = strchr(line, '=');
|
||||
if (!equal || (size_t)(equal - line) >= key_cap || strlen(equal + 1) >= value_cap) return -1;
|
||||
memcpy(key, line, (size_t)(equal - line));
|
||||
key[equal - line] = '\0';
|
||||
config_ini_trim(key);
|
||||
strcpy(value, equal + 1);
|
||||
char *comment = strchr(value, '#');
|
||||
if (comment) *comment = '\0';
|
||||
config_ini_trim(value);
|
||||
return key[0] ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Проверить физические строки до разбора, чтобы не принять хвост длинной строки за новую опцию. */
|
||||
static inline int config_ini_validate(const char *text, size_t length, const char *filename) {
|
||||
size_t column = 0, line = 1;
|
||||
if (!text && length) return -1;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (!text[i] || (text[i] != '\n' && ++column >= CONFIG_INI_LINE_SIZE - 1)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%zu: INI contains NUL or a line longer than 1022 bytes", filename, line);
|
||||
return -1;
|
||||
}
|
||||
if (text[i] == '\n') { column = 0; line++; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Прочитать весь поток, проверяя ошибки чтения/роста. Поток закрывает вызывающий; 0/-1. */
|
||||
static inline int config_ini_read(FILE *file, const char *filename, char **text, size_t *length) {
|
||||
struct strbuf output = strbuf_new();
|
||||
char chunk[4096];
|
||||
size_t count;
|
||||
*text = NULL;
|
||||
*length = 0;
|
||||
while ((count = fread(chunk, 1, sizeof(chunk), file)) != 0) {
|
||||
if (memchr(chunk, '\0', count) || output.len > SIZE_MAX - count - 1 ||
|
||||
strbuf_addf(&output, "%.*s", (int)count, chunk) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: cannot read INI text (NUL, size overflow or allocation failure)", filename);
|
||||
strbuf_free(&output);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (ferror(file) || config_ini_validate(strbuf_str(&output), output.len, filename)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: configuration read/validation failed", filename);
|
||||
strbuf_free(&output);
|
||||
return -1;
|
||||
}
|
||||
if (!output.buf && strbuf_addf(&output, "%s", "") < 0) { strbuf_free(&output); return -1; }
|
||||
*length = output.len;
|
||||
*text = strbuf_detach(&output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int config_ini_nibble(unsigned char ch) {
|
||||
if (ch >= '0' && ch <= '9') return ch - '0';
|
||||
if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
|
||||
if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Проверить весь HEX-фрагмент до записи. binary=NULL — только проверка; 0/-1. */
|
||||
static inline int config_ini_hex_span(const char *text, size_t length, uint8_t *binary, size_t size) {
|
||||
if (!text || size > SIZE_MAX / 2 || length != size * 2) return -1;
|
||||
for (size_t i = 0; i < length; i++) if (config_ini_nibble((unsigned char)text[i]) < 0) return -1;
|
||||
if (binary) {
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
binary[i] = (uint8_t)((config_ini_nibble((unsigned char)text[i * 2]) << 4) |
|
||||
config_ini_nibble((unsigned char)text[i * 2 + 1]));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int config_ini_hex(const char *text, uint8_t *binary, size_t size) {
|
||||
return text ? config_ini_hex_span(text, strlen(text), binary, size) : -1;
|
||||
}
|
||||
|
||||
/* Список HEX через запятую: пустой допустим; непустые элементы имеют item_size байтов.
|
||||
* Только полностью корректный список копируется в binary; при ошибке *count=0. */
|
||||
static inline int config_ini_hex_list(const char *text, uint8_t *binary, size_t item_size, int max_count, int *count) {
|
||||
*count = 0;
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
const char *cursor = text;
|
||||
int items = 0;
|
||||
if (!*cursor) return 0;
|
||||
do {
|
||||
const char *end = strchr(cursor, ',');
|
||||
const char *next = end ? end + 1 : NULL;
|
||||
if (!end) end = cursor + strlen(cursor);
|
||||
while (cursor < end && isspace((unsigned char)*cursor)) cursor++;
|
||||
while (end > cursor && isspace((unsigned char)end[-1])) end--;
|
||||
if (items >= max_count || config_ini_hex_span(cursor, (size_t)(end - cursor),
|
||||
pass && binary ? binary + (size_t)items * item_size : NULL, item_size)) return -1;
|
||||
items++;
|
||||
cursor = next;
|
||||
} while (cursor);
|
||||
if (pass) *count = items;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
+38
-401
@@ -1,6 +1,7 @@
|
||||
// config_parser.c - Configuration parser for utun application (updated for new structures)
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "config_parser.h"
|
||||
#include "config_ini.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -25,16 +26,6 @@
|
||||
static int assign_string(char *dest, size_t dest_size, const char *src);
|
||||
static int parse_firewall_rule(const char *rule_str, struct global_config *global);
|
||||
|
||||
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) {
|
||||
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1;
|
||||
for (size_t i = 0; i < binary_len; i++) {
|
||||
unsigned int byte;
|
||||
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1;
|
||||
binary[i] = (uint8_t)byte;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
SECTION_UNKNOWN,
|
||||
SECTION_GLOBAL,
|
||||
@@ -58,38 +49,6 @@ typedef enum {
|
||||
SECTION_STUN
|
||||
} section_type_t;
|
||||
|
||||
static char* trim(char *str) {
|
||||
if (!str) return NULL;
|
||||
while (isspace((unsigned char)*str)) str++;
|
||||
char *end = str + strlen(str) - 1;
|
||||
while (end > str && isspace((unsigned char)*end)) end--;
|
||||
*(end + 1) = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
static int parse_key_value(const char *line, char *key, size_t key_len, char *value, size_t value_len) {
|
||||
char *equal = strchr(line, '=');
|
||||
if (!equal) return -1;
|
||||
|
||||
size_t key_size = equal - line;
|
||||
if (key_size >= key_len) return -1;
|
||||
strncpy(key, line, key_size);
|
||||
key[key_size] = '\0';
|
||||
trim(key);
|
||||
|
||||
const char *val_start = equal + 1;
|
||||
size_t val_len = strlen(val_start);
|
||||
if (val_len >= value_len) return -1;
|
||||
strcpy(value, val_start);
|
||||
|
||||
char *comment = strchr(value, '#');
|
||||
if (comment) *comment = '\0';
|
||||
|
||||
trim(value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int assign_string(char *dest, size_t dest_size, const char *src) {
|
||||
if (!dest || !src || strlen(src) >= dest_size) return -1;
|
||||
strcpy(dest, src);
|
||||
@@ -248,7 +207,7 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa
|
||||
char rule_copy[64];
|
||||
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
|
||||
rule_copy[sizeof(rule_copy) - 1] = '\0';
|
||||
trim(rule_copy);
|
||||
config_ini_trim(rule_copy);
|
||||
|
||||
if (strcasecmp(rule_copy, "all") == 0) {
|
||||
global->firewall_bypass_all = 1;
|
||||
@@ -301,7 +260,7 @@ static int parse_control_allow(const char *rule_str, struct global_config *globa
|
||||
char rule_copy[64];
|
||||
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
|
||||
rule_copy[sizeof(rule_copy) - 1] = '\0';
|
||||
trim(rule_copy);
|
||||
config_ini_trim(rule_copy);
|
||||
|
||||
uint8_t cidr = 32;
|
||||
char *slash = strchr(rule_copy, '/');
|
||||
@@ -535,7 +494,7 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
|
||||
char buf[256];
|
||||
strncpy(buf, value, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
trim(buf);
|
||||
config_ini_trim(buf);
|
||||
|
||||
char* local_port_str = strtok(buf, " ");
|
||||
char* arrow = strtok(NULL, " ");
|
||||
@@ -599,7 +558,7 @@ static int parse_nat(const char *key, const char *value, struct global_config *g
|
||||
char buf[128];
|
||||
strncpy(buf, value, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
trim(buf);
|
||||
config_ini_trim(buf);
|
||||
|
||||
char* proto_str = strtok(buf, ":");
|
||||
char* ip_str = strtok(NULL, ":");
|
||||
@@ -756,14 +715,14 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
|
||||
}
|
||||
if (strcmp(key, "server_name") == 0) { return assign_string(cli->reality.server_name, sizeof(cli->reality.server_name), value); }
|
||||
if (strcmp(key, "short_id") == 0) {
|
||||
if (hex_to_binary(value, cli->reality.short_id, REALITY_SHORT_ID_SIZE) < 0) {
|
||||
if (config_ini_hex(value, cli->reality.short_id, REALITY_SHORT_ID_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: client reality short_id must be %d hex chars", filename, line_num, REALITY_SHORT_ID_SIZE * 2);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "public_key") == 0) {
|
||||
if (hex_to_binary(value, cli->reality.server_static_pubkey, REALITY_AUTH_KEY_SIZE) < 0) {
|
||||
if (config_ini_hex(value, cli->reality.server_static_pubkey, REALITY_AUTH_KEY_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: client reality public_key must be %d hex chars", filename, line_num, REALITY_AUTH_KEY_SIZE * 2);
|
||||
return -1;
|
||||
}
|
||||
@@ -867,34 +826,23 @@ static int parse_reality(const char *key, const char *value, struct reality_conf
|
||||
if (strcmp(key, "server_name") == 0) { return assign_string(rc->server_name, sizeof(rc->server_name), value); }
|
||||
if (strcmp(key, "dest") == 0) { return assign_string(rc->dest, sizeof(rc->dest), value); }
|
||||
if (strcmp(key, "short_id") == 0) {
|
||||
if (hex_to_binary(value, rc->short_id, REALITY_SHORT_ID_SIZE) < 0) {
|
||||
if (config_ini_hex(value, rc->short_id, REALITY_SHORT_ID_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: reality short_id must be %d hex chars", filename, line_num, REALITY_SHORT_ID_SIZE * 2);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "short_ids") == 0) {
|
||||
rc->short_id_count = 0;
|
||||
char buf[MAX_LINE_LEN];
|
||||
assign_string(buf, sizeof(buf), value);
|
||||
char *tok = strtok(buf, ",");
|
||||
while (tok) {
|
||||
char *t = trim(tok);
|
||||
if (rc->short_id_count >= REALITY_MAX_SHORT_IDS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: too many reality short_ids (max %d)", filename, line_num, REALITY_MAX_SHORT_IDS);
|
||||
return -1;
|
||||
}
|
||||
if (hex_to_binary(t, rc->short_ids[rc->short_id_count], REALITY_SHORT_ID_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: bad reality short_id '%s'", filename, line_num, t);
|
||||
return -1;
|
||||
}
|
||||
rc->short_id_count++;
|
||||
tok = strtok(NULL, ",");
|
||||
if (config_ini_hex_list(value, &rc->short_ids[0][0], REALITY_SHORT_ID_SIZE,
|
||||
REALITY_MAX_SHORT_IDS, &rc->short_id_count)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: invalid reality short_ids list", filename, line_num);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "public_key") == 0) {
|
||||
if (hex_to_binary(value, rc->public_key, REALITY_AUTH_KEY_SIZE) < 0) {
|
||||
rc->has_public_key = 0;
|
||||
if (config_ini_hex(value, rc->public_key, REALITY_AUTH_KEY_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: reality public_key must be %d hex chars", filename, line_num, REALITY_AUTH_KEY_SIZE * 2);
|
||||
return -1;
|
||||
}
|
||||
@@ -902,7 +850,8 @@ static int parse_reality(const char *key, const char *value, struct reality_conf
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "private_key") == 0) {
|
||||
if (hex_to_binary(value, rc->private_key, REALITY_AUTH_KEY_SIZE) < 0) {
|
||||
rc->has_private_key = 0;
|
||||
if (config_ini_hex(value, rc->private_key, REALITY_AUTH_KEY_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: reality private_key must be %d hex chars", filename, line_num, REALITY_AUTH_KEY_SIZE * 2);
|
||||
return -1;
|
||||
}
|
||||
@@ -1002,17 +951,9 @@ static int parse_stun(const char* key, const char* value, struct global_config*
|
||||
}
|
||||
|
||||
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
|
||||
if (line[0] != '[') return SECTION_UNKNOWN;
|
||||
size_t line_len = strlen(line);
|
||||
if (line[line_len - 1] != ']') return SECTION_UNKNOWN;
|
||||
|
||||
char section[128];
|
||||
if (line_len - 2 >= sizeof(section)) return SECTION_UNKNOWN;
|
||||
|
||||
strncpy(section, line + 1, line_len - 2);
|
||||
section[line_len - 2] = '\0';
|
||||
trim(section);
|
||||
|
||||
if (config_ini_section(line, section, sizeof(section))) return SECTION_UNKNOWN;
|
||||
|
||||
if (strcasecmp(section, "global") == 0) return SECTION_GLOBAL;
|
||||
if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING;
|
||||
if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG;
|
||||
@@ -1035,8 +976,8 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
if (!colon) return SECTION_UNKNOWN;
|
||||
|
||||
*colon = '\0';
|
||||
char *type = trim(section);
|
||||
char *n = trim(colon + 1);
|
||||
char *type = config_ini_trim(section);
|
||||
char *n = config_ini_trim(colon + 1);
|
||||
|
||||
if (strlen(n) >= name_len) return SECTION_UNKNOWN;
|
||||
strcpy(name, n);
|
||||
@@ -1048,313 +989,6 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
return SECTION_UNKNOWN;
|
||||
}
|
||||
|
||||
static struct utun_config* parse_config_internal(FILE *fp, const char *filename) {
|
||||
struct utun_config *cfg = u_calloc(1, sizeof(struct utun_config));
|
||||
if (!cfg) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "failed to allocate memory for config structure");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set default values
|
||||
cfg->global.name[0] = '\0';
|
||||
cfg->global.mtu = 1280; // Default MTU: safe for IPv6 tunnels
|
||||
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
|
||||
cfg->global.keepalive_interval = 200; // Default 0.2 s
|
||||
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
|
||||
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.auto_sockets = 0; // Default: disabled, use manual [server] sockets
|
||||
cfg->global.auto_socket_skip_no_default_route = 1; // Default: в авто-режиме пропускать интерфейсы без default-маршрута
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.tcp_proxy_server_connect_timeout_ms = 2000; // 2 с, 0 = без таймаута
|
||||
cfg->global.firewall_rules = NULL;
|
||||
cfg->global.firewall_rule_count = 0;
|
||||
cfg->global.firewall_bypass_all = 0;
|
||||
cfg->global.control_allows = NULL;
|
||||
cfg->global.control_allow_count = 0;
|
||||
cfg->global.db_sync_enabled = 0;
|
||||
cfg->global.db_sync_ttl = 86400;
|
||||
cfg->global.ntp_enabled = 0;
|
||||
cfg->global.ntp_servers = NULL;
|
||||
cfg->global.ntp_server_count = 0;
|
||||
cfg->global.ntp_resync_interval = 3600;
|
||||
cfg->global.chatserver_enabled = 0;
|
||||
cfg->global.chatserver_join_password[0] = '\0';
|
||||
cfg->global.chatserver_allowed_groups[0] = '\0';
|
||||
cfg->global.chatserver_storage_total_size = 0;
|
||||
cfg->global.chatserver_storage_unit_size = 0;
|
||||
reality_config_set_defaults(&cfg->global.reality);
|
||||
|
||||
section_type_t cur_section = SECTION_UNKNOWN;
|
||||
struct CFG_SERVER *cur_server = NULL;
|
||||
struct CFG_CLIENT *cur_client = NULL;
|
||||
struct CFG_NETWORK *cur_network = NULL;
|
||||
char line[MAX_LINE_LEN];
|
||||
int line_num = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
line_num++;
|
||||
char *trimmed = trim(line);
|
||||
|
||||
if (trimmed[0] == '\0' || trimmed[0] == '#') continue;
|
||||
|
||||
if (trimmed[0] == '[') {
|
||||
// Handle previous section
|
||||
if (cur_section == SECTION_SERVER && cur_server) {
|
||||
// Add server to linked list
|
||||
cur_server->next = cfg->servers;
|
||||
cfg->servers = cur_server;
|
||||
cur_server = NULL;
|
||||
}
|
||||
if (cur_section == SECTION_CLIENT && cur_client) {
|
||||
// Add client to linked list
|
||||
cur_client->next = cfg->clients;
|
||||
cfg->clients = cur_client;
|
||||
cur_client = NULL;
|
||||
}
|
||||
if (cur_section == SECTION_NETWORK && cur_network) {
|
||||
cur_network->next = cfg->networks;
|
||||
cfg->networks = cur_network;
|
||||
cur_network = NULL;
|
||||
}
|
||||
|
||||
char name[MAX_CONN_NAME_LEN];
|
||||
cur_section = parse_section_header(trimmed, name, sizeof(name));
|
||||
|
||||
if (cur_section == SECTION_SERVER) {
|
||||
cur_server = u_calloc(1, sizeof(struct CFG_SERVER));
|
||||
if (!cur_server) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "failed to allocate memory for server %s", name);
|
||||
goto error;
|
||||
}
|
||||
strcpy(cur_server->name, name);
|
||||
cur_server->fib = -1;
|
||||
} else if (cur_section == SECTION_CLIENT) {
|
||||
cur_client = u_calloc(1, sizeof(struct CFG_CLIENT));
|
||||
if (!cur_client) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "failed to allocate memory for client %s", name);
|
||||
goto error;
|
||||
}
|
||||
strcpy(cur_client->name, name);
|
||||
reality_client_config_set_defaults(&cur_client->reality);
|
||||
} else if (cur_section == SECTION_NETWORK) {
|
||||
cur_network = u_calloc(1, sizeof(struct CFG_NETWORK));
|
||||
if (!cur_network) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "failed to allocate memory for network %s", name);
|
||||
goto error;
|
||||
}
|
||||
strcpy(cur_network->name, name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
char key[MAX_LINE_LEN], value[MAX_LINE_LEN];
|
||||
if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key=value format", filename, line_num);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (cur_section) {
|
||||
case SECTION_GLOBAL:
|
||||
if (parse_global(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_global already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_SERVER:
|
||||
if (cur_server && parse_server(key, value, cur_server, filename, line_num) < 0) {
|
||||
// parse_server already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_CLIENT:
|
||||
if (cur_client && parse_client(key, value, cur_client, cfg->servers, filename, line_num) < 0) {
|
||||
// parse_client already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_ROUTING:
|
||||
if (strcmp(key, "route_subnet") == 0) {
|
||||
add_route_entry(&cfg->route_subnets, value);
|
||||
} else if (strcmp(key, "my_subnet") == 0) {
|
||||
add_route_entry(&cfg->my_subnets, value);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown routing option '%s'. Valid: route_subnet, my_subnet", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_DEBUG:
|
||||
// Format: category=level (e.g., etcp=debug, crypto=info)
|
||||
if (cfg->global.debug_levels.count < DEBUG_CATEGORY_COUNT) {
|
||||
strncpy(cfg->global.debug_levels.category[cfg->global.debug_levels.count],
|
||||
key, 15);
|
||||
cfg->global.debug_levels.category[cfg->global.debug_levels.count][15] = '\0';
|
||||
strncpy(cfg->global.debug_levels.level[cfg->global.debug_levels.count],
|
||||
value, 15);
|
||||
cfg->global.debug_levels.level[cfg->global.debug_levels.count][15] = '\0';
|
||||
cfg->global.debug_levels.count++;
|
||||
} else DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Too many debug categories, ignored %s=%s", filename, line_num, key, value);
|
||||
break;
|
||||
case SECTION_FIREWALL:
|
||||
if (strcmp(key, "allow") == 0) {
|
||||
if (parse_firewall_rule(value, &cfg->global) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid firewall rule: %s", filename, line_num, value);
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown firewall option '%s'. Valid: allow", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_CONTROL:
|
||||
if (parse_control(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_control already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_ALLOWED_KEYS:
|
||||
if (strcmp(key, "allow_all") == 0) {
|
||||
cfg->global.allowed_keys_allow_all = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
} else if (strcmp(key, "key") == 0) {
|
||||
struct CFG_ALLOWED_KEY *new_key = u_calloc(1, sizeof(struct CFG_ALLOWED_KEY));
|
||||
if (!new_key) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to allocate allowed key");
|
||||
} else if (strlen(value) != SC_PUBKEY_SIZE * 2) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key length %zu, expected %d", filename, line_num, strlen(value), SC_PUBKEY_SIZE * 2);
|
||||
u_free(new_key);
|
||||
} else if (hex_to_binary(value, new_key->key_bin, SC_PUBKEY_SIZE) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid hex key format", filename, line_num);
|
||||
u_free(new_key);
|
||||
} else {
|
||||
new_key->next = cfg->global.allowed_keys;
|
||||
cfg->global.allowed_keys = new_key;
|
||||
cfg->global.allowed_keys_count++;
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown allowed_keys option '%s'. Valid: allow_all, key", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_NAT:
|
||||
cfg->global.nat_enabled = 1;
|
||||
if (parse_nat(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_nat already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_TCP_PROXY_CLIENT:
|
||||
cfg->global.tcp_proxy_client_enabled = 1;
|
||||
if (parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_tcp_proxy_client already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_TCP_PROXY_SERVER:
|
||||
cfg->global.tcp_proxy_server_enabled = 1;
|
||||
if (strcmp(key, "tcp_recv_buf") == 0) {
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
} else if (strcmp(key, "fib") == 0) {
|
||||
cfg->global.tcp_proxy_server_fib = atoi(value);
|
||||
} else if (strcmp(key, "so_mark") == 0) {
|
||||
cfg->global.tcp_proxy_server_so_mark = atoi(value);
|
||||
} else if (strcmp(key, "netif") == 0) {
|
||||
cfg->global.tcp_proxy_server_netif_index = get_netif_index(value);
|
||||
} else if (strcmp(key, "bind_ip") == 0) {
|
||||
strncpy(cfg->global.tcp_proxy_server_bind_ip, value, sizeof(cfg->global.tcp_proxy_server_bind_ip) - 1);
|
||||
cfg->global.tcp_proxy_server_bind_ip[sizeof(cfg->global.tcp_proxy_server_bind_ip) - 1] = '\0';
|
||||
} else if (strcmp(key, "connect_timeout") == 0) {
|
||||
cfg->global.tcp_proxy_server_connect_timeout_ms = atoi(value);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_server option '%s'. Valid: tcp_recv_buf, fib, so_mark, netif, bind_ip, connect_timeout", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_NETWORK:
|
||||
if (cur_network && parse_network(key, value, cur_network, filename, line_num) < 0) {
|
||||
// parse_network already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_NTP:
|
||||
if (strcmp(key, "enabled") == 0) {
|
||||
cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
} else if (strcmp(key, "server") == 0) {
|
||||
struct CFG_NTP_SERVER *ns = u_calloc(1, sizeof(struct CFG_NTP_SERVER));
|
||||
if (ns) {
|
||||
strncpy(ns->name, value, sizeof(ns->name) - 1);
|
||||
ns->name[sizeof(ns->name) - 1] = '\0';
|
||||
ns->next = cfg->global.ntp_servers;
|
||||
cfg->global.ntp_servers = ns;
|
||||
cfg->global.ntp_server_count++;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "%s:%d: Failed to allocate NTP server entry", filename, line_num);
|
||||
}
|
||||
} else if (strcmp(key, "interval") == 0) {
|
||||
cfg->global.ntp_resync_interval = atoi(value);
|
||||
if (cfg->global.ntp_resync_interval < 60) cfg->global.ntp_resync_interval = 60;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'. Valid: enabled, server, interval", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_CHATSERVER:
|
||||
cfg->global.chatserver_enabled = 1;
|
||||
cfg->global.db_sync_enabled = 1;
|
||||
if (strcmp(key, "db_path") == 0) {
|
||||
assign_string(cfg->global.db_path, sizeof(cfg->global.db_path), value);
|
||||
} else if (parse_chatserver(key, value, &cfg->global, filename, line_num) < 0 &&
|
||||
chat_setting_set_state(&cfg->global.chat_settings, key, value) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown chatserver option '%s'. Valid: db_path, join_password, allowed_groups, storage_total_size, storage_unit_size, storage_autoload, opus_codec_preset, radio_opus_mode, radio_opus_bitrate, radio_capture_stereo, compressor_enabled, compressor_max_gain_db, compressor_rise_rate, radio_compressor_enabled, radio_compressor_max_gain_db, media_download_max_peers", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_GUI:
|
||||
break;
|
||||
case SECTION_REALITY:
|
||||
parse_reality(key, value, &cfg->global.reality, filename, line_num);
|
||||
break;
|
||||
case SECTION_SOCKS:
|
||||
parse_socks(key, value, &cfg->global, filename, line_num);
|
||||
break;
|
||||
case SECTION_STUN:
|
||||
if (parse_stun(key, value, &cfg->global, filename, line_num) < 0) goto error;
|
||||
break;
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg->global.stun_enabled && cfg->global.stun_server_count < 2) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s: enabled STUN requires at least two servers", filename);
|
||||
goto error;
|
||||
}
|
||||
// Handle final sections
|
||||
if (cur_section == SECTION_SERVER && cur_server) {
|
||||
cur_server->next = cfg->servers;
|
||||
cfg->servers = cur_server;
|
||||
}
|
||||
if (cur_section == SECTION_CLIENT && cur_client) {
|
||||
cur_client->next = cfg->clients;
|
||||
cfg->clients = cur_client;
|
||||
}
|
||||
if (cur_section == SECTION_NETWORK && cur_network) {
|
||||
cur_network->next = cfg->networks;
|
||||
cfg->networks = cur_network;
|
||||
}
|
||||
|
||||
// Post-parse defaults: tun_enabled=1 if not explicitly disabled
|
||||
if (!cfg->global.tun_enabled && cfg->global.tun_ifname[0] == '\0' && cfg->global.tun_ip.family == AF_UNSPEC) {
|
||||
// No TUN config at all — treat as explicitly off (for non-root SOCKS/HTTP-only mode)
|
||||
} else if (!cfg->global.tun_enabled) {
|
||||
// TUN was explicitly disabled via tun_enabled=no
|
||||
} else {
|
||||
// TUN enabled (default) — set default ifname if not provided
|
||||
cfg->global.tun_enabled = 1;
|
||||
if (cfg->global.tun_ifname[0] == '\0') {
|
||||
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
|
||||
error:
|
||||
if (cur_server) u_free(cur_server);
|
||||
if (cur_client) {
|
||||
free_cfg_client_links(cur_client->links);
|
||||
u_free(cur_client);
|
||||
}
|
||||
if (cur_network) u_free(cur_network);
|
||||
free_config(cfg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── Buffer line reader for parse_config_from_buf ── */
|
||||
typedef struct { const char *buf; size_t len; size_t pos; } blr_t;
|
||||
static int blr_get_line(blr_t *blr, char *line, size_t maxlen) {
|
||||
@@ -1370,6 +1004,8 @@ static int blr_get_line(blr_t *blr, char *line, size_t maxlen) {
|
||||
}
|
||||
|
||||
struct utun_config* parse_config_from_buf(const char *buf, size_t len, const char *filename) {
|
||||
if (!filename) filename = "<buffer>";
|
||||
if (config_ini_validate(buf, len, filename)) return NULL;
|
||||
struct utun_config *cfg = u_calloc(1, sizeof(struct utun_config));
|
||||
if (!cfg) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "failed to allocate memory for config structure"); return NULL; }
|
||||
cfg->global.name[0] = '\0';
|
||||
@@ -1381,6 +1017,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
cfg->global.auto_sockets = 0;
|
||||
cfg->global.auto_socket_skip_no_default_route = 1;
|
||||
cfg->global.tcp_recv_buf = 0;
|
||||
cfg->global.tcp_proxy_server_connect_timeout_ms = 2000;
|
||||
cfg->global.firewall_rules = NULL;
|
||||
cfg->global.firewall_rule_count = 0;
|
||||
cfg->global.firewall_bypass_all = 0;
|
||||
@@ -1409,7 +1046,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
|
||||
while (blr_get_line(&blr, line, sizeof(line))) {
|
||||
line_num++;
|
||||
char *trimmed = trim(line);
|
||||
char *trimmed = config_ini_trim(line);
|
||||
if (trimmed[0] == '\0' || trimmed[0] == '#') continue;
|
||||
if (trimmed[0] == '[') {
|
||||
if (cur_section == SECTION_SERVER && cur_server) { cur_server->next = cfg->servers; cfg->servers = cur_server; cur_server = NULL; }
|
||||
@@ -1423,7 +1060,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
continue;
|
||||
}
|
||||
char key[MAX_LINE_LEN], value[MAX_LINE_LEN];
|
||||
if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) < 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key=value format", filename, line_num); continue; }
|
||||
if (config_ini_key_value(trimmed, key, sizeof(key), value, sizeof(value)) < 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key=value format", filename, line_num); continue; }
|
||||
switch (cur_section) {
|
||||
case SECTION_GLOBAL: parse_global(key, value, &cfg->global, filename, line_num); break;
|
||||
case SECTION_SERVER: if (cur_server) parse_server(key, value, cur_server, filename, line_num); break;
|
||||
@@ -1438,7 +1075,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
break;
|
||||
case SECTION_FIREWALL: if (strcmp(key, "allow") == 0) parse_firewall_rule(value, &cfg->global); else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown firewall option '%s'", filename, line_num, key); break;
|
||||
case SECTION_CONTROL: parse_control(key, value, &cfg->global, filename, line_num); break;
|
||||
case SECTION_ALLOWED_KEYS: if (strcmp(key, "allow_all") == 0) cfg->global.allowed_keys_allow_all = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "key") == 0) { struct CFG_ALLOWED_KEY *nk = u_calloc(1, sizeof(struct CFG_ALLOWED_KEY)); if (nk && strlen(value) == SC_PUBKEY_SIZE * 2 && hex_to_binary(value, nk->key_bin, SC_PUBKEY_SIZE) == 0) { nk->next = cfg->global.allowed_keys; cfg->global.allowed_keys = nk; cfg->global.allowed_keys_count++; } else u_free(nk); } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown allowed_keys option '%s'", filename, line_num, key); break;
|
||||
case SECTION_ALLOWED_KEYS: if (strcmp(key, "allow_all") == 0) cfg->global.allowed_keys_allow_all = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "key") == 0) { struct CFG_ALLOWED_KEY *nk = u_calloc(1, sizeof(struct CFG_ALLOWED_KEY)); if (nk && strlen(value) == SC_PUBKEY_SIZE * 2 && config_ini_hex(value, nk->key_bin, SC_PUBKEY_SIZE) == 0) { nk->next = cfg->global.allowed_keys; cfg->global.allowed_keys = nk; cfg->global.allowed_keys_count++; } else u_free(nk); } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown allowed_keys option '%s'", filename, line_num, key); break;
|
||||
case SECTION_NAT: cfg->global.nat_enabled = 1; parse_nat(key, value, &cfg->global, filename, line_num); break;
|
||||
case SECTION_TCP_PROXY_CLIENT: cfg->global.tcp_proxy_client_enabled = 1; parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num); break;
|
||||
case SECTION_TCP_PROXY_SERVER:
|
||||
@@ -1493,17 +1130,17 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Файл и буфер проходят один разбор; ошибки чтения/закрытия не дают частичный конфиг. */
|
||||
struct utun_config* parse_config(const char *filename) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Opening config file: %s", filename);
|
||||
FILE *fp = fopen(filename, "r");
|
||||
if (!fp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to open config file: %s (errno=%d)", filename, errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Successfully opened config file");
|
||||
struct utun_config *config = parse_config_internal(fp, filename);
|
||||
fclose(fp);
|
||||
if (!filename || !*filename) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "empty config filename"); return NULL; }
|
||||
FILE *file = fopen(filename, "rb");
|
||||
if (!file) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "cannot open config %s: %s", filename, strerror(errno)); return NULL; }
|
||||
char *text = NULL;
|
||||
size_t length = 0;
|
||||
int rc = config_ini_read(file, filename, &text, &length);
|
||||
if (fclose(file)) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "cannot close config %s: %s", filename, strerror(errno)); rc = -1; }
|
||||
struct utun_config *config = rc ? NULL : parse_config_from_buf(text, length, filename);
|
||||
u_free(text);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1695,7 +1332,7 @@ void print_chatserver_invite_link(const struct utun_config *config, const struct
|
||||
char pub_hex_first[SC_PUBKEY_SIZE * 2 + 1];
|
||||
memcpy(pub_hex_first, g->my_public_key_hex, SC_PUBKEY_SIZE * 2);
|
||||
pub_hex_first[SC_PUBKEY_SIZE * 2] = '\0';
|
||||
if (hex_to_binary(pub_hex_first, pub_bin, SC_PUBKEY_SIZE) < 0) {
|
||||
if (config_ini_hex(pub_hex_first, pub_bin, SC_PUBKEY_SIZE) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "print_chatserver_invite_link: invalid pubkey hex");
|
||||
return;
|
||||
}
|
||||
@@ -1824,7 +1461,7 @@ int config_peer_in_clients(const struct utun_config* config, const uint8_t* pubk
|
||||
uint8_t pk_bin[SC_PUBKEY_SIZE];
|
||||
for (const struct CFG_CLIENT* cli = config->clients; cli; cli = cli->next) {
|
||||
if (!cli->peer_public_key_hex[0]) continue;
|
||||
if (hex_to_binary(cli->peer_public_key_hex, pk_bin, SC_PUBKEY_SIZE) != 0) continue;
|
||||
if (config_ini_hex(cli->peer_public_key_hex, pk_bin, SC_PUBKEY_SIZE) != 0) continue;
|
||||
if (memcmp(pk_bin, pubkey, SC_PUBKEY_SIZE) == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
Reference in new issue
Block a user