From 9a7091b595f4e4fe1a42f9ce49413d914fc3752c Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 26 Oct 2026 00:26:10 +0100 Subject: [PATCH] Document strict parser ownership and validate provided peer keys --- src/config_parser.c | 6 ++++++ src/config_parser.h | 47 ++++++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 70eaace7..9e210958 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -360,6 +360,9 @@ 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_private_key") || !strcmp(key, "my_public_key")) && *value && config_ini_hex(value, NULL, SC_PRIVKEY_SIZE)) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: %s requires 64 HEX characters", filename, line_num, key); return -1; + } 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); @@ -735,6 +738,9 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c } if (strcmp(key, "peer_public_key") == 0) { + if (*value && config_ini_hex(value, NULL, SC_PUBKEY_SIZE)) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: peer_public_key requires 64 HEX characters", filename, line_num); return -1; + } return assign_string(cli->peer_public_key_hex, MAX_KEY_LEN, value); } diff --git a/src/config_parser.h b/src/config_parser.h index cac434fd..524eb9b9 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -3,9 +3,13 @@ * Успех возвращает выделенную utun_config со списками; освобождение — только free_config. * Файл и буфер проходят один разбор. Пробелы вокруг секций, ключей и значений удаляются; * имена секций нечувствительны к регистру, имена ключей чувствительны. Комментарий — #. - * HEX проверяется целиком; ошибочный список REALITY short_ids не применяется частично. + * Ошибка обработчика известной опции отклоняет весь конфиг с освобождением частичной модели. + * HEX проверяется целиком; network требует оба ключа. Неизвестные секции игнорируются с WARN; + * [gui] обрабатывает только utun_enabled. Числа проверяются до сужения, bool — yes/no/true/false/1/0. + * Повторные значения применяются по порядку; ошибочный ранний дубль также отклоняется. * Встроенный NUL и строки >1022 байтов до LF запрещены (CR входит в этот предел). - * Парсер заполняет модель, запуск сетевых ресурсов выполняет utun_instance. */ + * Парсер не открывает сетевые сокеты, но синхронно разрешает addr/control через getaddrinfo; + * на Windows вызывающий заранее инициализирует socket subsystem. Сервисы запускает utun_instance. */ #ifndef CONFIG_PARSER_H #define CONFIG_PARSER_H @@ -26,7 +30,7 @@ extern "C" { #define MAX_ADDR_LEN 64 struct IP { - sa_family_t family; + sa_family_t family; // AF_INET/AF_INET6, 0 — адрес не задан; байты addr в сетевом порядке union { struct in_addr v4; struct in6_addr v6; @@ -55,32 +59,32 @@ struct IP { #define CFG_IPV6_MODE_PERMANENT 2 // permanent IPv6 from interface struct CFG_SERVER { - struct CFG_SERVER* next; - char name[MAX_CONN_NAME_LEN]; + struct CFG_SERVER* next; // собственная цепочка конфигурации; список в обратном порядке секций + char name[MAX_CONN_NAME_LEN]; // имя [server:name], до 63 байт struct sockaddr_storage ip; // ip:port uint32_t netif_index;// if_nameindex, 0 - no interface specified int so_mark; int fib; // FreeBSD FIB (routing table), -1 = don't set - uint8_t type; // public/nat/private + uint8_t type; // CFG_SERVER_TYPE_*; 0 если явно не задан uint8_t transport; // 0=udp (default), 1=tcp uint8_t reality_enabled; // 1 = TCP-порт с REALITY-камуфляжем (только transport=1) uint8_t socks_enabled; // 1 = UDP-сокет туннелировать через SOCKS5 (только transport=0) uint8_t ipv6_mode; // CFG_IPV6_MODE_* - int mtu; + int mtu; // 0 — не задан, при явной опции 0..65535; ресурс проверяет применимость uint8_t only_local; // 1 = only local connections, no forwarding }; struct CFG_CLIENT_LINK { struct CFG_CLIENT_LINK *next; // Next link in linked list - struct CFG_SERVER* local_srv; - char server_name[MAX_CONN_NAME_LEN]; // Name of local server for this link + struct CFG_SERVER* local_srv; // заимствован у того же utun_config; NULL — remote-only + char server_name[MAX_CONN_NAME_LEN]; // копия имени local_srv; пуста для remote-only struct sockaddr_storage remote_addr; // ip:port }; struct CFG_CLIENT { char name[MAX_CONN_NAME_LEN]; char peer_public_key_hex[MAX_KEY_LEN]; - int keepalive; + int keepalive; // необязательная клиентская настройка; 0 по умолчанию, неотрицательный int struct CFG_CLIENT_LINK *links; // Linked list of links struct CFG_CLIENT *next; // Next client in linked list uint8_t reality_enabled; // 1 = TCP-линк с REALITY-камуфляжем @@ -97,8 +101,8 @@ struct CFG_NETWORK { struct CFG_ROUTE_ENTRY { struct CFG_ROUTE_ENTRY* next; - struct IP ip; - uint8_t netmask; + struct IP ip; // исходный адрес подсети (host-биты парсер не нормализует) + uint8_t netmask; // префикс 0..32/0..128; без / по умолчанию 32/128 по семейству }; struct CFG_FIREWALL_RULE { @@ -117,15 +121,15 @@ struct CFG_ALLOWED_KEY { }; struct CFG_NTP_SERVER { - char name[256]; + char name[256]; // NUL-terminated hostname/IP; парсер не разрешает его, список идёт в обратном порядке struct CFG_NTP_SERVER *next; }; /* STUN-серверы обходятся в порядке конфигурации; DNS выполняет модуль stun_nat. */ struct CFG_STUN_SERVER { struct CFG_STUN_SERVER* next; - char host[256]; - uint16_t port; + char host[256]; // IPv4/имя A-сервера; DNS выполняет stun_nat + uint16_t port; // 1..65535, по умолчанию 3478 }; #define MAX_TCP_PROXY_CLIENT_MAPPINGS 32 @@ -273,16 +277,23 @@ struct utun_config { struct CFG_NETWORK* networks; }; -/* Непустой путь; результат освобождать free_config. NULL при ошибке открытия, чтения, - * закрытия, памяти или обязательной проверки формата, включая ошибки [global]. Часть опций других секций только - * логируется и пропускается; ненулевой результат не гарантирует корректность всех полей. */ +/* Непустой путь; результат освобождать free_config. NULL при ошибке открытия/чтения/закрытия, + * памяти, формата или обработчика известной опции. Значения по умолчанию и полноту настройки + * для конкретного сервиса дополнительно проверяет его start; ненулевой результат не гарантирует запуск. + * Файл не изменяется, ключи не генерируются: для этого отдельно вызвать config_ensure_keys_and_node_id. */ struct utun_config* parse_config(const char *filename); /* buf — len байтов без завершающего NUL (NULL допустим при len=0), буфер не меняется. * filename — источник для диагностики, NULL означает . Возврат как у parse_config. */ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const char *filename); +// Освобождает модель и все собственные списки/массивы; заимствованные ссылки становятся недействительны; NULL допустим. void free_config(struct utun_config *config); +// Пишет диагностический снимок в config/INFO; private/signing keys не выводятся; NULL — no-op. void print_config(const struct utun_config *config); +// Печатает в config/INFO и stderr bootstrap utun:// v3 с channel_id/join_key=0 по адресам servers (до 32). +// Чат должен быть включён; используются конфигурационные адреса, не фактический bind/NAT. NULL — no-op. void print_chatserver_invite_link(const struct utun_config *config, const struct CFG_SERVER *servers); +// Устаревшее добавление [global] с двумя 32-byte HEX ключами в конец файла; 0/-1 с логом. +// Не атомарно, частичная запись возможна. Для согласованных ключей/node_id использовать config_updater. int update_config_keys(const char *filename, const char *priv_key, const char *pub_key); /* Узел (по pubkey, 32 байта) присутствует в списке [client] конфига? 1=да, 0=нет. */