You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

524 lines
21 KiB

// config_updater.c - Configuration file updater implementation
#define _POSIX_C_SOURCE 200809L
#include "config_updater.h"
#include "config_parser.h"
#include "secure_channel.h"
#include "debug_config.h"
#include "../lib/platform_compat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include "../lib/mem.h"
#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
#define PUB_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
#define HEXNODEID_LEN 17 // 8 bytes * 2 hex chars + null
#define MAX_LINE_LEN 1024
void bytes_to_hex(const uint8_t *bytes, size_t len, char *hex_str, size_t hex_len) {
if (!bytes || !hex_str || hex_len < len * 2 + 1) {
if (hex_str && hex_len > 0) hex_str[0] = '\0';
return;
}
for (size_t i = 0; i < len; i++) {
snprintf(hex_str + i * 2, hex_len - i * 2, "%02x", bytes[i]);
}
hex_str[len * 2] = '\0';
}
static int is_valid_priv_key(const char *key) {
if (!key || strlen(key) != 64) return 0;
for (int i = 0; i < 64; i++) {
if (!isxdigit((unsigned char)key[i])) return 0;
}
return 1;
}
static int is_valid_pub_key(const char *key) {
if (!key || strlen(key) != 64) return 0;
for (int i = 0; i < 64; i++) {
if (!isxdigit((unsigned char)key[i])) return 0;
}
return 1;
}
static int is_valid_node_id(uint64_t node_id) {
return node_id != 0;
}
static int write_mem_to_file(const char *filename, const char *buffer, size_t size) {
FILE *fp = fopen(filename, "w");
if (!fp) return -1;
size_t written = fwrite(buffer, 1, size, fp);
if (written != size) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
static char* find_section_global(char *buf, size_t buf_len) {
char *p = buf;
while (p < buf + buf_len) {
if (p[0] == '[' && strncmp(p + 1, "global", 6) == 0) {
char *end = strchr(p, ']');
if (end) return end + 1;
}
p = strchr(p, '\n');
if (!p) break;
p++; // skip '\n'
}
return NULL;
}
static char* find_option(char *buf, size_t buf_len, const char *option) {
char *p = buf;
while (p < buf + buf_len) {
if (strncmp(p, option, strlen(option)) == 0) {
char *after_key = p + strlen(option);
if (after_key[0] == '=') return p;
}
p = strchr(p, '\n');
if (!p) break;
p++;
}
return NULL;
}
// Смещение байта сразу после ']' заголовка секции "[name]" (регистронезависимо), -1 если нет.
static long find_section_header(const char *buf, size_t buf_len, const char *name) {
size_t name_len = strlen(name);
size_t i = 0;
while (i < buf_len) {
if (buf[i] == '[' && i + name_len + 2 <= buf_len &&
strncasecmp(buf + i + 1, name, name_len) == 0 && buf[i + name_len + 1] == ']') {
return (long)(i + name_len + 2);
}
const char *nl = memchr(buf + i, '\n', buf_len - i);
if (!nl) break;
i = (size_t)(nl - buf) + 1;
}
return -1;
}
// Смещение строки, начинающейся с "option=", в диапазоне [start, end), -1 если нет.
static long find_option_in_range(const char *buf, size_t start, size_t end, const char *option) {
size_t olen = strlen(option);
size_t i = start;
while (i < end) {
if (i + olen <= end && strncmp(buf + i, option, olen) == 0 && buf[i + olen] == '=') {
return (long)i;
}
const char *nl = memchr(buf + i, '\n', end - i);
if (!nl) break;
i = (size_t)(nl - buf) + 1;
}
return -1;
}
// Заменить строку option=value внутри секции [section], либо вставить её сразу после
// строки заголовка секции (без пустых строк). Возвращает 0 / -1.
static int insert_or_replace_in_section(char **buf, size_t *buf_len, size_t *buf_capacity,
const char *section, const char *option, const char *value) {
if (!buf || !*buf || !buf_len || !buf_capacity || !section || !option || !value) return -1;
long hdr_off = find_section_header(*buf, *buf_len, section);
if (hdr_off < 0) return -1;
size_t sec_start = (size_t)hdr_off;
char *next_sec = memchr(*buf + sec_start, '[', *buf_len - sec_start);
size_t sec_end = next_sec ? (size_t)(next_sec - *buf) : *buf_len;
long opt_off = find_option_in_range(*buf, sec_start, sec_end, option);
if (opt_off >= 0) {
size_t opos = (size_t)opt_off;
char *nl = memchr(*buf + opos, '\n', sec_end - opos);
int has_trailing_nl = (nl != NULL);
size_t line_end = nl ? (size_t)(nl - *buf) + 1 : sec_end;
size_t old_len = line_end - opos;
char new_line[MAX_LINE_LEN];
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s%s", option, value, has_trailing_nl ? "\n" : "");
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
long len_diff = (long)new_len - (long)old_len;
if ((long)*buf_len + len_diff + 1 > (long)*buf_capacity) {
*buf_capacity = *buf_len + len_diff + 1024;
char *new_buf = u_realloc(*buf, *buf_capacity);
if (!new_buf) return -1;
*buf = new_buf;
hdr_off = find_section_header(*buf, *buf_len, section);
if (hdr_off < 0) return -1;
sec_start = (size_t)hdr_off;
next_sec = memchr(*buf + sec_start, '[', *buf_len - sec_start);
sec_end = next_sec ? (size_t)(next_sec - *buf) : *buf_len;
opt_off = find_option_in_range(*buf, sec_start, sec_end, option);
if (opt_off < 0) return -1;
opos = (size_t)opt_off;
nl = memchr(*buf + opos, '\n', sec_end - opos);
has_trailing_nl = (nl != NULL);
line_end = nl ? (size_t)(nl - *buf) + 1 : sec_end;
}
memmove(*buf + opos + new_len, *buf + line_end, *buf_len - line_end + 1);
memcpy(*buf + opos, new_line, new_len);
*buf_len += len_diff;
} else {
size_t ins = sec_start;
int need_leading_nl = 0;
if (ins < *buf_len && (*buf)[ins] == '\n') {
ins += 1;
} else {
need_leading_nl = 1;
}
char new_line[MAX_LINE_LEN];
int new_len = snprintf(new_line, sizeof(new_line), "%s%s=%s\n", need_leading_nl ? "\n" : "", option, value);
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
if (*buf_len + (size_t)new_len + 1 > *buf_capacity) {
*buf_capacity = *buf_len + (size_t)new_len + 1024;
char *new_buf = u_realloc(*buf, *buf_capacity);
if (!new_buf) return -1;
*buf = new_buf;
hdr_off = find_section_header(*buf, *buf_len, section);
if (hdr_off < 0) return -1;
ins = (size_t)hdr_off;
need_leading_nl = 0;
if (ins < *buf_len && (*buf)[ins] == '\n') {
ins += 1;
} else {
need_leading_nl = 1;
}
new_len = snprintf(new_line, sizeof(new_line), "%s%s=%s\n", need_leading_nl ? "\n" : "", option, value);
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
}
memmove(*buf + ins + new_len, *buf + ins, *buf_len - ins + 1);
memcpy(*buf + ins, new_line, new_len);
*buf_len += new_len;
}
return 0;
}
static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_capacity, const char *option, const char *value) {
if (!buf || !*buf || !buf_len || !buf_capacity || !option || !value) return -1;
// Check if option already exists
char *opt_pos = find_option(*buf, *buf_len, option);
if (opt_pos) {
// Determine end of old line (1 byte after \n or end of buffer)
char *nl = strchr(opt_pos, '\n');
int has_trailing_nl = (nl != NULL);
char *line_end = nl ? nl + 1 : *buf + *buf_len;
// Prepare new line
char new_line[MAX_LINE_LEN];
int new_len;
if (has_trailing_nl) {
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
} else {
new_len = snprintf(new_line, sizeof(new_line), "%s=%s", option, value);
}
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
// Calculate length difference
size_t old_line_len = line_end - opt_pos;
long len_diff = new_len - old_line_len;
// Ensure buffer capacity
if (*buf_len + len_diff + 1 > *buf_capacity) {
*buf_capacity = *buf_len + len_diff + 1024;
char *new_buf = u_realloc(*buf, *buf_capacity);
if (!new_buf) return -1;
*buf = new_buf;
// Recalculate positions after realloc
opt_pos = find_option(*buf, *buf_len, option);
if (!opt_pos) return -1;
nl = strchr(opt_pos, '\n');
has_trailing_nl = (nl != NULL);
line_end = nl ? nl + 1 : *buf + *buf_len;
}
// Move content and insert new line
memmove(opt_pos + new_len, line_end, *buf_len - (line_end - *buf) + 1);
memcpy(opt_pos, new_line, new_len);
*buf_len += len_diff;
} else {
// Insert after [global] section
char *section_end = find_section_global(*buf, *buf_len);
if (!section_end) return -1;
// Find end of global section (next [ or end of buffer)
char *insert_pos = strchr(section_end, '[');
int need_leading_nl = 0;
if (!insert_pos) {
insert_pos = *buf + *buf_len;
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
need_leading_nl = 1;
} else {
// Move to beginning of next line
char *prev_nl = insert_pos;
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
}
// Prepare new line
char new_line[MAX_LINE_LEN];
int new_len;
if (need_leading_nl)
new_len = snprintf(new_line, sizeof(new_line), "\n%s=%s\n", option, value);
else
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
// Ensure buffer capacity
if (*buf_len + new_len + 1 > *buf_capacity) {
*buf_capacity = *buf_len + new_len + 1024;
char *new_buf = u_realloc(*buf, *buf_capacity);
if (!new_buf) return -1;
*buf = new_buf;
// Recalculate insert position after realloc
section_end = find_section_global(*buf, *buf_len);
if (!section_end) return -1;
insert_pos = strchr(section_end, '[');
need_leading_nl = 0;
if (!insert_pos) {
insert_pos = *buf + *buf_len;
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
need_leading_nl = 1;
} else {
char *prev_nl = insert_pos;
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
}
}
// Move content and insert new line
memmove(insert_pos + new_len, insert_pos, *buf_len - (insert_pos - *buf) + 1);
memcpy(insert_pos, new_line, new_len);
*buf_len += new_len;
}
return 0;
}
int config_ensure_keys_and_node_id(const char *filename) {
// ── Read config file into memory (single open) ──
FILE *fp = fopen(filename, "rb");
char *file_buf = NULL;
size_t file_size = 0;
struct utun_config *config = NULL;
if (fp) {
fseek(fp, 0, SEEK_END); long fsz = ftell(fp); fseek(fp, 0, SEEK_SET);
if (fsz < 0) { fclose(fp); return -1; }
file_size = (size_t)fsz;
file_buf = u_malloc(file_size + 1);
if (!file_buf || fread(file_buf, 1, file_size, fp) != file_size) { u_free(file_buf); fclose(fp); return -1; }
file_buf[file_size] = '\0';
fclose(fp);
config = parse_config_from_buf(file_buf, file_size, filename);
if (!config) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse config from buffer: %s", filename); u_free(file_buf); return -1; }
} else {
// No file — create minimal config
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Config file not found, creating new: %s (errno=%d)", filename, errno);
config = u_calloc(1, sizeof(struct utun_config));
if (!config) return -1;
config->global.my_node_id = 0;
config->global.my_private_key_hex[0] = '\0';
config->global.my_public_key_hex[0] = '\0';
config->global.name[0] = '\0';
config->global.keepalive_timeout = 2000;
config->global.keepalive_interval = 200;
config->global.keepalive_adaptive = 1;
config->global.bbr_max_cwnd = 1048576;
}
struct global_config *global = &config->global;
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Checking config - priv_key='%s' (len=%zu), pub_key='%s' (len=%zu), node_id=%016llx",
global->my_private_key_hex[0] ? global->my_private_key_hex : "NULL",
global->my_private_key_hex[0] ? strlen(global->my_private_key_hex) : 0,
global->my_public_key_hex[0] ? global->my_public_key_hex : "NULL",
global->my_public_key_hex[0] ? strlen(global->my_public_key_hex) : 0,
(unsigned long long)global->my_node_id);
int need_priv_key = !is_valid_priv_key(global->my_private_key_hex);
int need_pub_key = 0;
int need_node_id = 0;
char new_priv_key[PRIV_HEXKEY_LEN] = {0};
char new_pub_key[PUB_HEXKEY_LEN] = {0};
uint64_t new_node_id = 0;
uint8_t priv_bin[SC_PRIVKEY_SIZE];
// ── Step 1: ensure valid privkey ──
if (need_priv_key) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Generating NEW keypair — private key was invalid/missing in %s", filename);
struct SC_MYKEYS mykeys;
if (sc_generate_keypair(&mykeys) != SC_OK) { free_config(config); u_free(file_buf); return -1; }
bytes_to_hex(mykeys.private_key, SC_PRIVKEY_SIZE, new_priv_key, sizeof(new_priv_key));
memcpy(priv_bin, mykeys.private_key, SC_PRIVKEY_SIZE);
} else {
for (int i = 0; i < SC_PRIVKEY_SIZE; i++) {
unsigned int byte;
if (sscanf(global->my_private_key_hex + i * 2, "%2x", &byte) != 1) { free_config(config); u_free(file_buf); return -1; }
priv_bin[i] = (uint8_t)byte;
}
bytes_to_hex(priv_bin, SC_PRIVKEY_SIZE, new_priv_key, sizeof(new_priv_key));
}
// ── Step 2: derive pubkey from privkey ──
uint8_t pub_bin[SC_PUBKEY_SIZE];
if (sc_compute_public_key_from_private(priv_bin, pub_bin) != SC_OK) { free_config(config); u_free(file_buf); return -1; }
bytes_to_hex(pub_bin, SC_PUBKEY_SIZE, new_pub_key, sizeof(new_pub_key));
if (!is_valid_pub_key(global->my_public_key_hex) || strcmp(global->my_public_key_hex, new_pub_key) != 0) {
need_pub_key = 1;
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Pubkey mismatch (or missing), fixing: config=%s derived=%s",
global->my_public_key_hex[0] ? global->my_public_key_hex : "NULL", new_pub_key);
}
// ── Step 3: derive node_id from pubkey ──
new_node_id = sc_derive_node_id_from_pubkey(pub_bin);
if (!is_valid_node_id(global->my_node_id) || global->my_node_id != new_node_id) {
need_node_id = 1;
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Node_id mismatch (or missing), fixing: config=%016llx derived=%016llx file=%s",
(unsigned long long)global->my_node_id, (unsigned long long)new_node_id, filename);
}
// ── Step 4: reality-камуфляж — private_key и short_ids при enabled=1 ──
int need_reality_priv = 0, need_reality_ids = 0;
char new_reality_priv_hex[REALITY_AUTH_KEY_SIZE * 2 + 1] = {0};
char new_short_ids_str[4 * (REALITY_SHORT_ID_SIZE * 2) + 4] = {0};
if (global->reality.enabled) {
if (!global->reality.has_private_key) {
need_reality_priv = 1;
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Reality private_key missing, generating new one in %s", filename);
uint8_t rpriv[REALITY_AUTH_KEY_SIZE], rpub[REALITY_AUTH_KEY_SIZE];
if (reality_generate_keypair(rpriv, rpub) != REALITY_OK) { free_config(config); u_free(file_buf); return -1; }
bytes_to_hex(rpriv, REALITY_AUTH_KEY_SIZE, new_reality_priv_hex, sizeof(new_reality_priv_hex));
}
if (global->reality.short_id_count == 0) {
need_reality_ids = 1;
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Reality short_ids missing, generating 4 ids in %s", filename);
char *p = new_short_ids_str;
size_t remain = sizeof(new_short_ids_str);
for (int i = 0; i < 4; i++) {
uint8_t sid[REALITY_SHORT_ID_SIZE];
if (random_bytes(sid, sizeof(sid)) != 0) { free_config(config); u_free(file_buf); return -1; }
char hex[REALITY_SHORT_ID_SIZE * 2 + 1];
bytes_to_hex(sid, REALITY_SHORT_ID_SIZE, hex, sizeof(hex));
int w = snprintf(p, remain, "%s%s", i ? "," : "", hex);
if (w < 0 || (size_t)w >= remain) { free_config(config); u_free(file_buf); return -1; }
p += w; remain -= (size_t)w;
}
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Validation results - need_priv_key=%d, need_pub_key=%d, need_node_id=%d, need_reality_priv=%d, need_reality_ids=%d",
need_priv_key, need_pub_key, need_node_id, need_reality_priv, need_reality_ids);
if (!need_priv_key && !need_pub_key && !need_node_id && !need_reality_priv && !need_reality_ids) {
free_config(config); u_free(file_buf); return 0;
}
char cfg_name[MAX_CONN_NAME_LEN]; snprintf(cfg_name, sizeof(cfg_name), "%s", global->name[0] ? global->name : "utun");
free_config(config);
char node_id_hex[HEXNODEID_LEN + 1];
snprintf(node_id_hex, sizeof(node_id_hex), "%016llx", (unsigned long long)new_node_id);
// ── Modify/create config buffer ──
char *work_buf = NULL; size_t work_len = 0; size_t buf_capacity = 0;
int is_new_file = (!file_buf || !file_size);
if (is_new_file) {
buf_capacity = 2048;
work_buf = u_malloc(buf_capacity);
if (!work_buf) { u_free(file_buf); return -1; }
work_len = (size_t)snprintf(work_buf, buf_capacity,
"[global]\nmy_node_name=%s\nmy_private_key=%s\nmy_public_key=%s\nmy_node_id=%s\n",
cfg_name, new_priv_key, new_pub_key, node_id_hex);
u_free(file_buf); file_buf = NULL;
} else {
buf_capacity = file_size + 1024;
work_buf = u_malloc(buf_capacity);
if (!work_buf) { u_free(file_buf); return -1; }
memcpy(work_buf, file_buf, file_size + 1); /* Helpers use strchr; preserve the terminating NUL. */
work_len = file_size;
u_free(file_buf); file_buf = NULL;
}
int ret = 0;
if (!is_new_file) {
int write_keys = (need_priv_key || need_pub_key || need_node_id);
if (need_node_id) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing my_node_id=%s to %s", node_id_hex, filename);
if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_node_id", node_id_hex) < 0) ret = -1;
}
if (write_keys && ret == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing my_private_key to %s", filename);
if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_private_key", new_priv_key) < 0) ret = -1;
}
if (write_keys && ret == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing my_public_key to %s", filename);
if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_public_key", new_pub_key) < 0) ret = -1;
}
if (need_reality_ids && ret == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing reality short_ids to %s", filename);
if (insert_or_replace_in_section(&work_buf, &work_len, &buf_capacity, "reality", "short_ids", new_short_ids_str) < 0) ret = -1;
}
if (need_reality_priv && ret == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing reality private_key to %s", filename);
if (insert_or_replace_in_section(&work_buf, &work_len, &buf_capacity, "reality", "private_key", new_reality_priv_hex) < 0) ret = -1;
}
}
if (ret == 0 && work_buf) {
if (write_mem_to_file(filename, work_buf, work_len) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to write updated config file: %s", filename);
ret = -1;
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Config file updated: %s (gen_priv=%d gen_pub=%d gen_nodeid=%d gen_reality_priv=%d gen_reality_ids=%d)", filename, need_priv_key, need_pub_key, need_node_id, need_reality_priv, need_reality_ids);
}
}
u_free(work_buf);
return ret;
}