diff --git a/src/config_parser.c b/src/config_parser.c index 0b53c590..c04a0cbf 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1047,6 +1047,99 @@ error: 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) { + if (blr->pos >= blr->len) return 0; + size_t i = 0; + while (blr->pos < blr->len && i < maxlen - 1) { + char c = blr->buf[blr->pos++]; + line[i++] = c; + if (c == '\n') break; + } + line[i] = '\0'; + return 1; +} + +struct utun_config* parse_config_from_buf(const char *buf, size_t len, const char *filename) { + struct utun_config *cfg = u_calloc(1, sizeof(struct utun_config)); + if (!cfg) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for config structure"); return NULL; } + cfg->global.name[0] = '\0'; + cfg->global.keepalive_timeout = 2000; + cfg->global.keepalive_interval = 200; + cfg->global.keepalive_adaptive = 1; + cfg->global.bbr_max_cwnd = 1048576; + cfg->global.tcp_recv_buf = 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_server_count = 0; + cfg->global.ntp_resync_interval = 3600; + + 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; + blr_t blr = {buf, len, 0}; + char line[MAX_LINE_LEN]; + int line_num = 0; + + while (blr_get_line(&blr, line, sizeof(line))) { + line_num++; + char *trimmed = 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; } + if (cur_section == SECTION_CLIENT && cur_client) { 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) 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) goto error; strcpy(cur_client->name, name); } + else if (cur_section == SECTION_NETWORK) { cur_network = u_calloc(1, sizeof(struct CFG_NETWORK)); if (!cur_network) 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: 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; + case SECTION_CLIENT: if (cur_client) parse_client(key, value, cur_client, cfg->servers, filename, line_num); 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'", filename, line_num, key); break; + case SECTION_DEBUG: if (cfg->global.debug_levels.count < 16) { 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++; } 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_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: cfg->global.tcp_proxy_server_enabled = 1; if (strcmp(key, "tcp_recv_buf") == 0) cfg->global.tcp_recv_buf = atoi(value); break; + case SECTION_MSG_TRANSPORT: parse_msg_transport(key, value, &cfg->global, filename, line_num); break; + case SECTION_NETWORK: if (cur_network) parse_network(key, value, cur_network, filename, line_num); 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 && cfg->global.ntp_server_count < 5) { int idx = cfg->global.ntp_server_count; strncpy(cfg->global.ntp_servers[idx], value, sizeof(cfg->global.ntp_servers[idx]) - 1); cfg->global.ntp_servers[idx][sizeof(cfg->global.ntp_servers[idx]) - 1] = '\0'; cfg->global.ntp_server_count++; } 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'", filename, line_num, key); break; + case SECTION_GUI: break; + default: DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key); break; + } + } + 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; } + if (!cfg->global.tun_enabled && cfg->global.tun_ifname[0] == '\0' && cfg->global.tun_ip.family == AF_UNSPEC) {} + else if (!cfg->global.tun_enabled) {} + else { cfg->global.tun_enabled = 1; if (cfg->global.tun_ifname[0] == '\0') { snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0"); } } + 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; +} + struct utun_config* parse_config(const char *filename) { DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Opening config file: %s", filename); FILE *fp = fopen(filename, "r"); diff --git a/src/config_parser.h b/src/config_parser.h index 858b4bdd..89f46062 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -200,6 +200,7 @@ struct utun_config { }; struct utun_config* parse_config(const char *filename); +struct utun_config* parse_config_from_buf(const char *buf, size_t len, const char *filename); void free_config(struct utun_config *config); void print_config(const struct utun_config *config); int update_config_keys(const char *filename, const char *priv_key, const char *pub_key); diff --git a/src/config_updater.c b/src/config_updater.c index 69a58b56..13e62df4 100644 --- a/src/config_updater.c +++ b/src/config_updater.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "../lib/mem.h" #define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null @@ -53,48 +54,6 @@ static int is_valid_node_id(uint64_t node_id) { return node_id != 0; } -static int read_file_to_mem(const char *filename, char **buffer, size_t *size) { - FILE *fp = fopen(filename, "r"); - - if (!fp) return -1; - - fseek(fp, 0, SEEK_END); - - long file_size = ftell(fp); - - if (file_size < 0) { - fclose(fp); - - return -1; - } - fseek(fp, 0, SEEK_SET); - - - *buffer = u_malloc(file_size + 1); - - if (!*buffer) { - fclose(fp); - - return -1; - } - - size_t read_size = fread(*buffer, 1, file_size, fp); - - if (read_size != (size_t)file_size) { - u_free(*buffer); - - fclose(fp); - - return -1; - } - - (*buffer)[file_size] = '\0'; - *size = file_size; - fclose(fp); - - return 0; -} - static int write_mem_to_file(const char *filename, const char *buffer, size_t size) { FILE *fp = fopen(filename, "w"); @@ -261,30 +220,35 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap } int config_ensure_keys_and_node_id(const char *filename) { - struct utun_config *config = parse_config(filename); - - if (!config) { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse config: %s", filename); - + // ── Read config file into memory (single open) ── + FILE *fp = fopen(filename, "rb"); + if (!fp) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to open config file: %s (errno=%d)", filename, errno); return -1; } - + fseek(fp, 0, SEEK_END); long fsz = ftell(fp); fseek(fp, 0, SEEK_SET); + if (fsz < 0) { fclose(fp); return -1; } + size_t file_size = (size_t)fsz; + char *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); + + struct utun_config *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; } + struct global_config *global = &config->global; - - // Debug: print what we found + 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] ? 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); - // Check if we need to generate anything int need_priv_key = !is_valid_priv_key(global->my_private_key_hex); int need_pub_key = 0; int need_node_id = 0; - - // Generate keys if needed char new_priv_key[PRIV_HEXKEY_LEN] = {0}; char new_pub_key[PUB_HEXKEY_LEN] = {0}; uint64_t new_node_id = 0; @@ -294,34 +258,21 @@ int config_ensure_keys_and_node_id(const char *filename) { 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) { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to generate keypair"); - free_config(config); - return -1; - } + 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 { - // Convert existing privkey hex → binary for (int i = 0; i < SC_PRIVKEY_SIZE; i++) { unsigned int byte; - if (sscanf(global->my_private_key_hex + i * 2, "%2x", &byte) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid private key hex format"); - free_config(config); - return -1; - } + 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 (always check) ── + // ── 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) { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to compute public key from private key"); - free_config(config); - return -1; - } + 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; @@ -330,15 +281,8 @@ int config_ensure_keys_and_node_id(const char *filename) { } // ── Step 3: derive node_id from privkey via SHA-256 ── - { - uint8_t sha_hash[32]; - SC_SHA256_CTX ctx; - sc_sha256_init(&ctx); - sc_sha256_update(&ctx, priv_bin, SC_PRIVKEY_SIZE); - sc_sha256_final(&ctx, sha_hash); - memcpy(&new_node_id, sha_hash, 8); - new_node_id &= 0x7FFFFFFFFFFFFFFFULL; - } + { uint8_t sha_hash[32]; SC_SHA256_CTX ctx; sc_sha256_init(&ctx); sc_sha256_update(&ctx, priv_bin, SC_PRIVKEY_SIZE); sc_sha256_final(&ctx, sha_hash); + memcpy(&new_node_id, sha_hash, 8); new_node_id &= 0x7FFFFFFFFFFFFFFFULL; } 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", @@ -348,36 +292,17 @@ int config_ensure_keys_and_node_id(const char *filename) { DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Validation results - need_priv_key=%d, need_pub_key=%d, need_node_id=%d", need_priv_key, need_pub_key, need_node_id); - if (!need_priv_key && !need_pub_key && !need_node_id) { - free_config(config); - return 0; - } + if (!need_priv_key && !need_pub_key && !need_node_id) { free_config(config); u_free(file_buf); return 0; } free_config(config); - - // Read entire config file to memory - char *file_buf = NULL; - size_t file_size = 0; - if (read_file_to_mem(filename, &file_buf, &file_size) < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to read config file: %s", filename); - - return -1; - } - - // Update config file + // ── Modify file buffer in place ── size_t buf_capacity = file_size + 1024; char *work_buf = u_malloc(buf_capacity); - - if (!work_buf) { - u_free(file_buf); - - return -1; - } + if (!work_buf) { u_free(file_buf); return -1; } memcpy(work_buf, file_buf, file_size); - + u_free(file_buf); size_t work_len = file_size; - int ret = 0; int write_keys = (need_priv_key || need_pub_key || need_node_id); @@ -385,43 +310,25 @@ int config_ensure_keys_and_node_id(const char *filename) { char node_id_hex[HEXNODEID_LEN + 1]; snprintf(node_id_hex, sizeof(node_id_hex), "%016llx", (unsigned long long)new_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 (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 (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 (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_public_key", new_pub_key) < 0) ret = -1; } - - if (ret == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Writing updated config file, work_len=%zu", work_len); + if (ret == 0) { 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)", filename, need_priv_key, need_pub_key, need_node_id); - } } - - u_free(file_buf); - u_free(work_buf); - - return ret; }