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
14 KiB
413 lines
14 KiB
// config_parser.c - Configuration parser for utun application |
|
#define _POSIX_C_SOURCE 200809L |
|
#include "config_parser.h" |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <strings.h> |
|
#include <ctype.h> |
|
#include <errno.h> |
|
|
|
#define MAX_LINE_LEN 1024 |
|
#define INITIAL_CONNECTION_CAPACITY 4 |
|
|
|
// Helper function to trim whitespace |
|
static char* trim(char *str) { |
|
if (!str) return NULL; |
|
|
|
// Trim leading spaces |
|
while (isspace((unsigned char)*str)) str++; |
|
|
|
// Trim trailing spaces |
|
char *end = str + strlen(str) - 1; |
|
while (end > str && isspace((unsigned char)*end)) end--; |
|
*(end + 1) = '\0'; |
|
|
|
return str; |
|
} |
|
|
|
// Parse a key-value pair |
|
static int parse_key_value(const char *line, char *key, size_t key_len, |
|
char *value, size_t value_len) { |
|
char *equal = strchr(line, '='); |
|
if (!equal) return -1; |
|
|
|
// Extract key |
|
size_t key_size = equal - line; |
|
if (key_size >= key_len) return -1; |
|
strncpy(key, line, key_size); |
|
key[key_size] = '\0'; |
|
trim(key); |
|
|
|
// Extract value |
|
const char *val_start = equal + 1; |
|
size_t val_len = strlen(val_start); |
|
if (val_len >= value_len) return -1; |
|
strcpy(value, val_start); |
|
trim(value); |
|
|
|
return 0; |
|
} |
|
|
|
// Parse connection mode string |
|
static config_conn_mode_t parse_mode(const char *mode_str) { |
|
if (!mode_str) return CONFIG_MODE_UNKNOWN; |
|
|
|
if (strcasecmp(mode_str, "client") == 0) { |
|
return CONFIG_MODE_CLIENT; |
|
} else if (strcasecmp(mode_str, "server") == 0) { |
|
return CONFIG_MODE_SERVER; |
|
} |
|
|
|
return CONFIG_MODE_UNKNOWN; |
|
} |
|
|
|
// Add a connection to the config |
|
static int add_connection(utun_config_t *config, const connection_config_t *conn) { |
|
if (config->connection_count >= config->connection_capacity) { |
|
// Resize array |
|
int new_capacity = config->connection_capacity * 2; |
|
connection_config_t *new_connections = realloc(config->connections, |
|
new_capacity * sizeof(connection_config_t)); |
|
if (!new_connections) return -1; |
|
|
|
config->connections = new_connections; |
|
config->connection_capacity = new_capacity; |
|
} |
|
|
|
// Copy connection data |
|
memcpy(&config->connections[config->connection_count], conn, sizeof(connection_config_t)); |
|
config->connection_count++; |
|
|
|
return 0; |
|
} |
|
|
|
// Parse configuration file |
|
utun_config_t* parse_config(const char *filename) { |
|
FILE *fp = fopen(filename, "r"); |
|
if (!fp) { |
|
fprintf(stderr, "Failed to open config file: %s\n", filename); |
|
return NULL; |
|
} |
|
|
|
// Allocate config structure |
|
utun_config_t *config = calloc(1, sizeof(utun_config_t)); |
|
if (!config) { |
|
fclose(fp); |
|
return NULL; |
|
} |
|
|
|
// Initialize connection array |
|
config->connection_capacity = INITIAL_CONNECTION_CAPACITY; |
|
config->connections = malloc(config->connection_capacity * sizeof(connection_config_t)); |
|
if (!config->connections) { |
|
free(config); |
|
fclose(fp); |
|
return NULL; |
|
} |
|
|
|
char line[MAX_LINE_LEN]; |
|
char current_section[128] = ""; |
|
connection_config_t current_conn = {0}; |
|
int in_connection_section = 0; |
|
|
|
while (fgets(line, sizeof(line), fp)) { |
|
// Remove newline |
|
line[strcspn(line, "\n")] = '\0'; |
|
|
|
char *trimmed = trim(line); |
|
|
|
// Skip empty lines and comments |
|
if (strlen(trimmed) == 0 || trimmed[0] == ';' || trimmed[0] == '#') { |
|
continue; |
|
} |
|
|
|
// Check for section header |
|
if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') { |
|
// End previous connection section if any |
|
if (in_connection_section) { |
|
if (strlen(current_conn.name) > 0) { |
|
if (add_connection(config, ¤t_conn) != 0) { |
|
fprintf(stderr, "Failed to add connection: %s\n", current_conn.name); |
|
} |
|
} |
|
memset(¤t_conn, 0, sizeof(current_conn)); |
|
in_connection_section = 0; |
|
} |
|
|
|
// Extract section name |
|
strncpy(current_section, trimmed + 1, sizeof(current_section) - 1); |
|
current_section[sizeof(current_section) - 1] = '\0'; |
|
current_section[strcspn(current_section, "]")] = '\0'; |
|
trim(current_section); |
|
|
|
// Check if it's a connection section |
|
if (strncmp(current_section, "connection:", 11) == 0) { |
|
in_connection_section = 1; |
|
// Extract connection name |
|
char *name = trim(current_section + 11); |
|
if (strlen(name) > 0) { |
|
strncpy(current_conn.name, name, sizeof(current_conn.name) - 1); |
|
} |
|
} |
|
|
|
continue; |
|
} |
|
|
|
// Parse key-value pair |
|
char key[256], value[256]; |
|
if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) != 0) { |
|
fprintf(stderr, "Invalid key-value line: %s\n", trimmed); |
|
continue; |
|
} |
|
|
|
// Process based on current section |
|
if (strcasecmp(current_section, "global") == 0) { |
|
if (strcasecmp(key, "my_private_key") == 0) { |
|
strncpy(config->global.my_private_key_hex, value, sizeof(config->global.my_private_key_hex) - 1); |
|
} else if (strcasecmp(key, "my_public_key") == 0) { |
|
strncpy(config->global.my_public_key_hex, value, sizeof(config->global.my_public_key_hex) - 1); |
|
} else if (strcasecmp(key, "option") == 0) { |
|
strncpy(config->global.option_value, value, sizeof(config->global.option_value) - 1); |
|
} else if (strcasecmp(key, "control_ip") == 0) { |
|
strncpy(config->global.control_ip, value, sizeof(config->global.control_ip) - 1); |
|
} else if (strcasecmp(key, "control_port") == 0) { |
|
config->global.control_port = atoi(value); |
|
} else if (strcasecmp(key, "net_debug") == 0) { |
|
config->global.net_debug = atoi(value); |
|
} |
|
} else if (strcasecmp(current_section, "routing") == 0) { |
|
if (strcasecmp(key, "allowed_subnet") == 0) { |
|
if (config->allowed_subnet_count < MAX_ALLOWED_SUBNETS) { |
|
strncpy(config->allowed_subnets[config->allowed_subnet_count].subnet, value, sizeof(config->allowed_subnets[0].subnet) - 1); |
|
config->allowed_subnet_count++; |
|
} else { |
|
fprintf(stderr, "Too many allowed subnets, maximum is %d\n", MAX_ALLOWED_SUBNETS); |
|
} |
|
} |
|
} else if (in_connection_section) { |
|
if (strcasecmp(key, "mode") == 0) { |
|
current_conn.mode = parse_mode(value); |
|
} else if (strcasecmp(key, "addr") == 0) { |
|
// For server mode |
|
if (current_conn.mode == CONFIG_MODE_SERVER) { |
|
strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1); |
|
} |
|
} else if (strcasecmp(key, "from_addr") == 0) { |
|
// For client mode |
|
if (current_conn.mode == CONFIG_MODE_CLIENT) { |
|
strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1); |
|
} |
|
} else if (strcasecmp(key, "to_addr") == 0) { |
|
// For client mode |
|
if (current_conn.mode == CONFIG_MODE_CLIENT) { |
|
strncpy(current_conn.remote_addr, value, sizeof(current_conn.remote_addr) - 1); |
|
} |
|
} else if (strcasecmp(key, "peer_public_key") == 0) { |
|
strncpy(current_conn.peer_public_key_hex, value, sizeof(current_conn.peer_public_key_hex) - 1); |
|
} else if (strcasecmp(key, "so_mark") == 0) { |
|
current_conn.so_mark = atoi(value); |
|
} else if (strcasecmp(key, "netif") == 0) { |
|
strncpy(current_conn.netif, value, sizeof(current_conn.netif) - 1); |
|
} else if (strcasecmp(key, "tun") == 0) { |
|
strncpy(current_conn.tun_ifname, value, sizeof(current_conn.tun_ifname) - 1); |
|
} else if (strcasecmp(key, "tun_ip") == 0) { |
|
strncpy(current_conn.tun_ip, value, sizeof(current_conn.tun_ip) - 1); |
|
} |
|
} |
|
} |
|
|
|
// Add last connection if any |
|
if (in_connection_section && strlen(current_conn.name) > 0) { |
|
if (add_connection(config, ¤t_conn) != 0) { |
|
fprintf(stderr, "Failed to add connection: %s\n", current_conn.name); |
|
} |
|
} |
|
|
|
fclose(fp); |
|
return config; |
|
} |
|
|
|
// Free configuration structure |
|
void free_config(utun_config_t *config) { |
|
if (!config) return; |
|
|
|
if (config->connections) { |
|
free(config->connections); |
|
} |
|
free(config); |
|
} |
|
|
|
// Print configuration for debugging |
|
void print_config(const utun_config_t *config) { |
|
if (!config) { |
|
printf("Configuration is NULL\n"); |
|
return; |
|
} |
|
|
|
printf("Global configuration:\n"); |
|
printf(" my_private_key: %s\n", config->global.my_private_key_hex); |
|
printf(" my_public_key: %s\n", config->global.my_public_key_hex); |
|
printf(" option: %s\n", config->global.option_value); |
|
printf(" control_ip: %s\n", config->global.control_ip); |
|
printf(" control_port: %u\n", config->global.control_port); |
|
printf(" net_debug: %d\n", config->global.net_debug); |
|
printf("\n"); |
|
printf("Allowed subnets (%d):\n", config->allowed_subnet_count); |
|
for (int i = 0; i < config->allowed_subnet_count; i++) { |
|
printf(" %s\n", config->allowed_subnets[i].subnet); |
|
} |
|
printf("\n"); |
|
|
|
printf("Connections (%d):\n", config->connection_count); |
|
for (int i = 0; i < config->connection_count; i++) { |
|
const connection_config_t *conn = &config->connections[i]; |
|
printf(" [%d] %s:\n", i, conn->name); |
|
printf(" mode: %s\n", |
|
conn->mode == CONFIG_MODE_CLIENT ? "client" : |
|
conn->mode == CONFIG_MODE_SERVER ? "server" : "unknown"); |
|
printf(" local_addr: %s\n", conn->local_addr); |
|
printf(" remote_addr: %s\n", conn->remote_addr); |
|
printf(" peer_public_key: %s\n", conn->peer_public_key_hex); |
|
printf(" so_mark: %d\n", conn->so_mark); |
|
printf(" netif: %s\n", conn->netif); |
|
printf(" tun: %s\n", conn->tun_ifname); |
|
printf(" tun_ip: %s\n", conn->tun_ip); |
|
} |
|
} |
|
|
|
// Update keys in configuration file |
|
int update_config_keys(const char *filename, |
|
const char *private_key_hex, |
|
const char *public_key_hex) { |
|
if (!filename || !private_key_hex || !public_key_hex) { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
// Read entire file |
|
FILE *fp = fopen(filename, "r"); |
|
if (!fp) { |
|
// Try to create new file |
|
fp = fopen(filename, "w"); |
|
if (!fp) return -1; |
|
|
|
fprintf(fp, "[global]\n"); |
|
fprintf(fp, "my_private_key=%s\n", private_key_hex); |
|
fprintf(fp, "my_public_key=%s\n", public_key_hex); |
|
fclose(fp); |
|
return 0; |
|
} |
|
|
|
// Read all lines |
|
char **lines = NULL; |
|
size_t line_count = 0; |
|
size_t capacity = 0; |
|
char line[MAX_LINE_LEN]; |
|
int in_global_section = 0; |
|
int private_key_found = 0; |
|
int public_key_found = 0; |
|
size_t private_key_line = 0; |
|
size_t public_key_line = 0; |
|
|
|
while (fgets(line, sizeof(line), fp)) { |
|
// Add line to array |
|
if (line_count >= capacity) { |
|
size_t new_capacity = capacity ? capacity * 2 : 16; |
|
char **new_lines = realloc(lines, new_capacity * sizeof(char *)); |
|
if (!new_lines) { |
|
// Cleanup |
|
for (size_t i = 0; i < line_count; i++) free(lines[i]); |
|
free(lines); |
|
fclose(fp); |
|
return -1; |
|
} |
|
lines = new_lines; |
|
capacity = new_capacity; |
|
} |
|
|
|
lines[line_count] = strdup(line); |
|
if (!lines[line_count]) { |
|
// Cleanup |
|
for (size_t i = 0; i < line_count; i++) free(lines[i]); |
|
free(lines); |
|
fclose(fp); |
|
return -1; |
|
} |
|
|
|
char *trimmed = trim(line); |
|
|
|
// Track global section |
|
if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') { |
|
char section[128]; |
|
strncpy(section, trimmed + 1, sizeof(section) - 1); |
|
section[sizeof(section) - 1] = '\0'; |
|
section[strcspn(section, "]")] = '\0'; |
|
trim(section); |
|
|
|
in_global_section = (strcasecmp(section, "global") == 0); |
|
} |
|
|
|
// Check for keys in global section |
|
if (in_global_section) { |
|
char key[256], value[256]; |
|
if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) == 0) { |
|
if (strcasecmp(key, "my_private_key") == 0) { |
|
private_key_found = 1; |
|
private_key_line = line_count; |
|
} else if (strcasecmp(key, "my_public_key") == 0) { |
|
public_key_found = 1; |
|
public_key_line = line_count; |
|
} |
|
} |
|
} |
|
|
|
line_count++; |
|
} |
|
fclose(fp); |
|
|
|
// Update or add keys |
|
if (private_key_found) { |
|
free(lines[private_key_line]); |
|
char new_line[MAX_LINE_LEN]; |
|
snprintf(new_line, sizeof(new_line), "my_private_key=%s\n", private_key_hex); |
|
lines[private_key_line] = strdup(new_line); |
|
} else { |
|
// Add after [global] line or at beginning |
|
// Simplified: add at end |
|
// We'll need to insert after [global] but for simplicity, append |
|
} |
|
|
|
if (public_key_found) { |
|
free(lines[public_key_line]); |
|
char new_line[MAX_LINE_LEN]; |
|
snprintf(new_line, sizeof(new_line), "my_public_key=%s\n", public_key_hex); |
|
lines[public_key_line] = strdup(new_line); |
|
} |
|
|
|
// Write back to file |
|
fp = fopen(filename, "w"); |
|
if (!fp) { |
|
// Cleanup |
|
for (size_t i = 0; i < line_count; i++) free(lines[i]); |
|
free(lines); |
|
return -1; |
|
} |
|
|
|
for (size_t i = 0; i < line_count; i++) { |
|
fputs(lines[i], fp); |
|
free(lines[i]); |
|
} |
|
|
|
// Add missing keys |
|
if (!private_key_found) { |
|
fprintf(fp, "my_private_key=%s\n", private_key_hex); |
|
} |
|
if (!public_key_found) { |
|
fprintf(fp, "my_public_key=%s\n", public_key_hex); |
|
} |
|
|
|
free(lines); |
|
fclose(fp); |
|
return 0; |
|
} |