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.
413 lines
12 KiB
413 lines
12 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 <ctype.h> |
|
#include <unistd.h> |
|
#include <fcntl.h> |
|
#include <sys/stat.h> |
|
|
|
#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null |
|
#define PUB_HEXKEY_LEN 129 // 64 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) != 128) return 0; |
|
for (int i = 0; i < 128; 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 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 = 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) { |
|
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"); |
|
|
|
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; |
|
} |
|
|
|
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) { |
|
// Find option line end |
|
char *line_end = strchr(opt_pos, '\n'); |
|
|
|
if (!line_end) line_end = *buf + *buf_len; |
|
|
|
// Prepare new line |
|
char new_line[MAX_LINE_LEN]; |
|
int 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; |
|
|
|
// Calculate length difference |
|
size_t old_line_len = line_end - opt_pos + 1; |
|
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 = 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; |
|
line_end = strchr(opt_pos, '\n'); |
|
|
|
if (!line_end) line_end = *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, '['); |
|
|
|
if (!insert_pos) insert_pos = *buf + *buf_len; |
|
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 = 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 = 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, '['); |
|
|
|
if (!insert_pos) insert_pos = *buf + *buf_len; |
|
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) { |
|
struct utun_config *config = parse_config(filename); |
|
|
|
if (!config) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse config: %s", filename); |
|
|
|
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=%llu", |
|
global->my_private_key_hex ? global->my_private_key_hex : "NULL", |
|
global->my_private_key_hex ? strlen(global->my_private_key_hex) : 0, |
|
global->my_public_key_hex ? global->my_public_key_hex : "NULL", |
|
global->my_public_key_hex ? 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 = !is_valid_pub_key(global->my_public_key_hex); |
|
|
|
int need_node_id = !is_valid_node_id(global->my_node_id); |
|
|
|
|
|
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; |
|
} |
|
|
|
// 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; |
|
|
|
if (need_priv_key) { |
|
// Generate new keypair if private key is invalid |
|
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; |
|
} |
|
bytes_to_hex(mykeys.private_key, SC_PRIVKEY_SIZE, new_priv_key, sizeof(new_priv_key)); |
|
|
|
bytes_to_hex(mykeys.public_key, SC_PUBKEY_SIZE, new_pub_key, sizeof(new_pub_key)); |
|
|
|
} else if (need_pub_key) { |
|
// Compute public key from existing private key |
|
uint8_t priv_bin[SC_PRIVKEY_SIZE]; |
|
uint8_t pub_bin[SC_PUBKEY_SIZE]; |
|
|
|
// Convert private key from hex to 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; |
|
} |
|
priv_bin[i] = (uint8_t)byte; |
|
} |
|
|
|
// Compute public key |
|
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; |
|
} |
|
|
|
// Convert to hex |
|
bytes_to_hex(priv_bin, SC_PRIVKEY_SIZE, new_priv_key, sizeof(new_priv_key)); |
|
|
|
bytes_to_hex(pub_bin, SC_PUBKEY_SIZE, new_pub_key, sizeof(new_pub_key)); |
|
|
|
} |
|
|
|
if (need_node_id) { |
|
if (random_bytes((uint8_t*)&new_node_id, sizeof(new_node_id)) != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to generate random node_id"); |
|
free_config(config); |
|
return -1; |
|
} |
|
new_node_id &= 0x7FFFFFFFFFFFFFFF; |
|
} |
|
|
|
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 |
|
size_t buf_capacity = file_size + 1024; |
|
char *work_buf = malloc(buf_capacity); |
|
|
|
if (!work_buf) { |
|
free(file_buf); |
|
|
|
return -1; |
|
} |
|
memcpy(work_buf, file_buf, file_size); |
|
|
|
size_t work_len = file_size; |
|
|
|
int ret = 0; |
|
|
|
if (need_node_id) { |
|
char node_id_hex[HEXNODEID_LEN + 1]; |
|
snprintf(node_id_hex, sizeof(node_id_hex), "%016llx", (unsigned long long)new_node_id); |
|
|
|
if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_node_id", node_id_hex) < 0) { |
|
ret = -1; |
|
} |
|
} |
|
|
|
if (need_priv_key && ret == 0) { |
|
if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_private_key", new_priv_key) < 0) { |
|
ret = -1; |
|
} |
|
} |
|
|
|
if (need_pub_key && ret == 0) { |
|
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 (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_DEBUG(DEBUG_CATEGORY_CONFIG, "Successfully updated config file: %s", filename); |
|
|
|
} |
|
} |
|
|
|
free(file_buf); |
|
|
|
free(work_buf); |
|
|
|
|
|
return ret; |
|
}
|
|
|