Files
utun2/src/config_updater.c
T

525 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;
}