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.
586 lines
19 KiB
586 lines
19 KiB
// config_parser.c - Configuration parser for utun application (updated for new structures) |
|
#define _POSIX_C_SOURCE 200809L |
|
#include "config_parser.h" |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <strings.h> |
|
#include "../lib/debug_config.h" |
|
#include <ctype.h> |
|
#include <arpa/inet.h> |
|
#include <errno.h> |
|
#include <netdb.h> |
|
#include <ifaddrs.h> |
|
#include <net/if.h> |
|
|
|
#define MAX_LINE_LEN 1024 |
|
#define INITIAL_ARRAY_CAPACITY 8 |
|
|
|
typedef enum { |
|
SECTION_UNKNOWN, |
|
SECTION_GLOBAL, |
|
SECTION_SERVER, |
|
SECTION_CLIENT, |
|
SECTION_ROUTING |
|
} section_type_t; |
|
|
|
static char* trim(char *str) { |
|
if (!str) return NULL; |
|
while (isspace((unsigned char)*str)) str++; |
|
char *end = str + strlen(str) - 1; |
|
while (end > str && isspace((unsigned char)*end)) end--; |
|
*(end + 1) = '\0'; |
|
return str; |
|
} |
|
|
|
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; |
|
|
|
size_t key_size = equal - line; |
|
if (key_size >= key_len) return -1; |
|
strncpy(key, line, key_size); |
|
key[key_size] = '\0'; |
|
trim(key); |
|
|
|
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; |
|
} |
|
|
|
static int assign_string(char *dest, size_t dest_size, const char *src) { |
|
if (!dest || !src || strlen(src) >= dest_size) return -1; |
|
strcpy(dest, src); |
|
return 0; |
|
} |
|
|
|
static int parse_ip_with_netmask(const char *str, struct IP *ip, uint8_t *netmask) { |
|
char ip_str[64]; |
|
strncpy(ip_str, str, sizeof(ip_str) - 1); |
|
ip_str[sizeof(ip_str) - 1] = '\0'; |
|
|
|
char *slash = strchr(ip_str, '/'); |
|
if (slash) { |
|
*slash = '\0'; |
|
*netmask = atoi(slash + 1); |
|
} else { |
|
*netmask = 32; // Default IPv4 netmask |
|
} |
|
|
|
// Try IPv4 first |
|
struct in_addr addr4; |
|
if (inet_pton(AF_INET, ip_str, &addr4) == 1) { |
|
ip->family = AF_INET; |
|
ip->addr.v4 = addr4; |
|
return 0; |
|
} |
|
|
|
// Try IPv6 |
|
struct in6_addr addr6; |
|
if (inet_pton(AF_INET6, ip_str, &addr6) == 1) { |
|
ip->family = AF_INET6; |
|
ip->addr.v6 = addr6; |
|
if (*netmask == 32) *netmask = 128; // Default IPv6 netmask |
|
return 0; |
|
} |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_ip_with_netmask: invalid IP address format: %s", str); |
|
return -1; |
|
} |
|
|
|
static int parse_sockaddr(const char *addr_str, const char *port_str, struct sockaddr_storage *sockaddr) { |
|
struct addrinfo hints = {0}; |
|
hints.ai_family = AF_UNSPEC; |
|
hints.ai_socktype = SOCK_DGRAM; |
|
|
|
struct addrinfo *result; |
|
if (getaddrinfo(addr_str, port_str, &hints, &result) != 0) { |
|
return -1; |
|
} |
|
|
|
memcpy(sockaddr, result->ai_addr, result->ai_addrlen); |
|
freeaddrinfo(result); |
|
return 0; |
|
} |
|
|
|
static int parse_address_and_port(const char *str, struct sockaddr_storage *sockaddr) { |
|
char addr_copy[MAX_ADDR_LEN]; |
|
if (strlen(str) >= sizeof(addr_copy)) return -1; |
|
strcpy(addr_copy, str); |
|
|
|
char *port_str = strrchr(addr_copy, ':'); |
|
if (!port_str) return -1; |
|
|
|
*port_str = '\0'; |
|
port_str++; |
|
|
|
return parse_sockaddr(addr_copy, port_str, sockaddr); |
|
} |
|
|
|
static uint32_t get_netif_index(const char *ifname) { |
|
if (!ifname || strlen(ifname) == 0) return 0; |
|
return if_nametoindex(ifname); |
|
} |
|
|
|
static struct CFG_CLIENT_LINK* create_client_link(struct CFG_SERVER* local_srv, const char *remote_addr) { |
|
struct CFG_CLIENT_LINK *link = calloc(1, sizeof(struct CFG_CLIENT_LINK)); |
|
if (!link) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "create_client_link: failed to allocate memory for client link"); |
|
return NULL; |
|
} |
|
|
|
link->local_srv = local_srv; |
|
|
|
if (parse_address_and_port(remote_addr, &link->remote_addr) < 0) { |
|
free(link); |
|
return NULL; |
|
} |
|
|
|
link->next = NULL; |
|
return link; |
|
} |
|
|
|
static void free_cfg_client_links(struct CFG_CLIENT_LINK *links) { |
|
while (links) { |
|
struct CFG_CLIENT_LINK *next = links->next; |
|
free(links); |
|
links = next; |
|
} |
|
} |
|
|
|
static struct CFG_ROUTE_ENTRY* create_route_entry(const char *subnet_str) { |
|
struct CFG_ROUTE_ENTRY *entry = calloc(1, sizeof(struct CFG_ROUTE_ENTRY)); |
|
if (!entry) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "create_route_entry: failed to allocate memory for route entry"); |
|
return NULL; |
|
} |
|
|
|
if (parse_ip_with_netmask(subnet_str, &entry->ip, &entry->netmask) < 0) { |
|
free(entry); |
|
return NULL; |
|
} |
|
|
|
entry->next = NULL; |
|
return entry; |
|
} |
|
|
|
static void free_route_entries(struct CFG_ROUTE_ENTRY *entries) { |
|
while (entries) { |
|
struct CFG_ROUTE_ENTRY *next = entries->next; |
|
free(entries); |
|
entries = next; |
|
} |
|
} |
|
|
|
static int add_route_entry(struct CFG_ROUTE_ENTRY **list, const char *subnet_str) { |
|
struct CFG_ROUTE_ENTRY *new_entry = create_route_entry(subnet_str); |
|
if (!new_entry) return -1; |
|
|
|
// Add to head of list |
|
new_entry->next = *list; |
|
*list = new_entry; |
|
return 0; |
|
} |
|
|
|
static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) { |
|
struct CFG_SERVER *srv = servers; |
|
while (srv) { |
|
if (strcmp(srv->name, name) == 0) { |
|
return srv; |
|
} |
|
srv = srv->next; |
|
} |
|
return NULL; |
|
} |
|
|
|
static int parse_global(const char *key, const char *value, struct global_config *global) { |
|
if (strcmp(key, "my_private_key") == 0) { |
|
return assign_string(global->my_private_key_hex, MAX_KEY_LEN, value); |
|
} |
|
if (strcmp(key, "my_public_key") == 0) { |
|
return assign_string(global->my_public_key_hex, MAX_KEY_LEN, value); |
|
} |
|
if (strcmp(key, "my_node_id") == 0) { |
|
global->my_node_id = strtoull(value, NULL, 16); |
|
return 0; |
|
} |
|
if (strcmp(key, "tun_ifname") == 0) { |
|
snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value); |
|
return 0; |
|
} |
|
if (strcmp(key, "tun_ip") == 0) { |
|
uint8_t netmask; |
|
return parse_ip_with_netmask(value, &global->tun_ip, &netmask); |
|
} |
|
if (strcmp(key, "mtu") == 0) { |
|
global->mtu = atoi(value); |
|
return 0; |
|
} |
|
if (strcmp(key, "control_ip") == 0) { |
|
// Store for later processing with control_port |
|
return 0; // We'll handle this when we see control_port |
|
} |
|
if (strcmp(key, "control_port") == 0) { |
|
// This is tricky - we need to get control_ip from previous parsing |
|
// For now, we'll use a simple approach |
|
struct global_config temp_global = *global; |
|
// Assume we stored control_ip somewhere or use default |
|
char control_ip[MAX_ADDR_LEN] = "127.0.0.1"; // Default |
|
char port_str[16]; |
|
snprintf(port_str, sizeof(port_str), "%s", value); |
|
parse_sockaddr(control_ip, port_str, &global->control_sock); |
|
return 0; |
|
} |
|
if (strcmp(key, "net_debug") == 0) { |
|
global->net_debug = atoi(value); |
|
return 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) { |
|
if (strcmp(key, "addr") == 0) { |
|
return parse_address_and_port(value, &srv->ip); |
|
} |
|
if (strcmp(key, "so_mark") == 0) { |
|
srv->so_mark = atoi(value); |
|
return 0; |
|
} |
|
if (strcmp(key, "netif") == 0) { |
|
srv->netif_index = get_netif_index(value); |
|
return 0; |
|
} |
|
if (strcmp(key, "type") == 0) { |
|
if (strcmp(value, "public") == 0) { |
|
srv->type = CFG_SERVER_TYPE_PUBLIC; |
|
} else if (strcmp(value, "nat") == 0) { |
|
srv->type = CFG_SERVER_TYPE_NAT; |
|
} else if (strcmp(value, "private") == 0) { |
|
srv->type = CFG_SERVER_TYPE_PRIVATE; |
|
} else { |
|
srv->type = CFG_SERVER_TYPE_UNKNOWN; |
|
} |
|
return 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers) { |
|
if (strcmp(key, "link") == 0) { |
|
char link_copy[MAX_CONN_NAME_LEN + MAX_ADDR_LEN]; |
|
if (strlen(value) >= sizeof(link_copy)) return -1; |
|
strcpy(link_copy, value); |
|
|
|
// Find first colon (separator between server and ip:port) |
|
char *first_colon = strchr(link_copy, ':'); |
|
if (!first_colon) return -1; |
|
|
|
*first_colon = '\0'; |
|
|
|
// Find server by name |
|
struct CFG_SERVER *local_srv = find_server_by_name(servers, link_copy); |
|
if (!local_srv) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_client: server '%s' not found for client link", link_copy); |
|
return -1; |
|
} |
|
|
|
struct CFG_CLIENT_LINK *new_link = create_client_link(local_srv, first_colon + 1); |
|
if (!new_link) return -1; |
|
|
|
// Add to linked list (prepend) |
|
new_link->next = cli->links; |
|
cli->links = new_link; |
|
|
|
return 0; |
|
} |
|
|
|
if (strcmp(key, "peer_public_key") == 0) { |
|
return assign_string(cli->peer_public_key_hex, MAX_KEY_LEN, value); |
|
} |
|
|
|
if (strcmp(key, "keepalive") == 0) { |
|
cli->keepalive = atoi(value); |
|
return 0; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) { |
|
if (line[0] != '[') return SECTION_UNKNOWN; |
|
|
|
size_t line_len = strlen(line); |
|
if (line[line_len - 1] != ']') return SECTION_UNKNOWN; |
|
|
|
char section[128]; |
|
if (line_len - 2 >= sizeof(section)) return SECTION_UNKNOWN; |
|
|
|
strncpy(section, line + 1, line_len - 2); |
|
section[line_len - 2] = '\0'; |
|
trim(section); |
|
|
|
if (strcasecmp(section, "global") == 0) return SECTION_GLOBAL; |
|
if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING; |
|
|
|
char *colon = strchr(section, ':'); |
|
if (!colon) return SECTION_UNKNOWN; |
|
|
|
*colon = '\0'; |
|
char *type = trim(section); |
|
char *n = trim(colon + 1); |
|
|
|
if (strlen(n) >= name_len) return SECTION_UNKNOWN; |
|
strcpy(name, n); |
|
|
|
if (strcasecmp(type, "server") == 0) return SECTION_SERVER; |
|
if (strcasecmp(type, "client") == 0) return SECTION_CLIENT; |
|
|
|
return SECTION_UNKNOWN; |
|
} |
|
|
|
static struct utun_config* parse_config_internal(FILE *fp, const char *filename) { |
|
struct utun_config *cfg = calloc(1, sizeof(struct utun_config)); |
|
if (!cfg) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for config structure"); |
|
return NULL; |
|
} |
|
|
|
section_type_t cur_section = SECTION_UNKNOWN; |
|
struct CFG_SERVER *cur_server = NULL; |
|
struct CFG_CLIENT *cur_client = NULL; |
|
char line[MAX_LINE_LEN]; |
|
int line_num = 0; |
|
|
|
while (fgets(line, sizeof(line), fp)) { |
|
line_num++; |
|
char *trimmed = trim(line); |
|
|
|
if (trimmed[0] == '\0' || trimmed[0] == '#') continue; |
|
|
|
if (trimmed[0] == '[') { |
|
// Handle previous section |
|
if (cur_section == SECTION_SERVER && cur_server) { |
|
// Add server to linked list |
|
cur_server->next = cfg->servers; |
|
cfg->servers = cur_server; |
|
cur_server = NULL; |
|
} |
|
if (cur_section == SECTION_CLIENT && cur_client) { |
|
// Add client to linked list |
|
cur_client->next = cfg->clients; |
|
cfg->clients = cur_client; |
|
cur_client = NULL; |
|
} |
|
|
|
char name[MAX_CONN_NAME_LEN]; |
|
cur_section = parse_section_header(trimmed, name, sizeof(name)); |
|
|
|
if (cur_section == SECTION_SERVER) { |
|
cur_server = calloc(1, sizeof(struct CFG_SERVER)); |
|
if (!cur_server) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for server %s", name); |
|
goto error; |
|
} |
|
strcpy(cur_server->name, name); |
|
} else if (cur_section == SECTION_CLIENT) { |
|
cur_client = calloc(1, sizeof(struct CFG_CLIENT)); |
|
if (!cur_client) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for client %s", name); |
|
goto error; |
|
} |
|
strcpy(cur_client->name, name); |
|
} |
|
continue; |
|
} |
|
|
|
char key[MAX_LINE_LEN], value[MAX_LINE_LEN]; |
|
if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) < 0) { |
|
printf( "%s:%d: Invalid key=value format", filename, line_num); |
|
continue; |
|
} |
|
|
|
switch (cur_section) { |
|
case SECTION_GLOBAL: |
|
if (parse_global(key, value, &cfg->global) < 0) { |
|
printf( "%s:%d: Invalid global key '%s'", filename, line_num, key); |
|
} |
|
break; |
|
case SECTION_SERVER: |
|
if (cur_server && parse_server(key, value, cur_server) < 0) { |
|
printf( "%s:%d: Invalid server key '%s'", filename, line_num, key); |
|
} |
|
break; |
|
case SECTION_CLIENT: |
|
if (cur_client && parse_client(key, value, cur_client, cfg->servers) < 0) { |
|
printf( "%s:%d: Invalid client key '%s'", filename, line_num, key); |
|
} |
|
break; |
|
case SECTION_ROUTING: |
|
if (strcmp(key, "allowed_subnet") == 0) { |
|
add_route_entry(&cfg->allowed_subnets, value); |
|
} else if (strcmp(key, "my_subnet") == 0) { |
|
add_route_entry(&cfg->my_subnets, value); |
|
} |
|
break; |
|
default: |
|
printf( "%s:%d: Key outside section: %s", filename, line_num, key); |
|
break; |
|
} |
|
} |
|
|
|
// Handle final sections |
|
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; |
|
} |
|
|
|
// Set default TUN interface name if not provided |
|
if (cfg->global.tun_ifname[0] == '\0') { |
|
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0"); |
|
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname); |
|
} |
|
|
|
return cfg; |
|
|
|
error: |
|
if (cur_server) free(cur_server); |
|
if (cur_client) { |
|
free_cfg_client_links(cur_client->links); |
|
free(cur_client); |
|
} |
|
free_config(cfg); |
|
return NULL; |
|
} |
|
|
|
struct utun_config* parse_config(const char *filename) { |
|
printf("[CONFIG DEBUG] Opening config file: %s\n", filename); |
|
FILE *fp = fopen(filename, "r"); |
|
if (!fp) { |
|
printf("[CONFIG ERROR] Failed to open config file: %s (errno=%d)\n", filename, errno); |
|
return NULL; |
|
} |
|
|
|
printf("[CONFIG DEBUG] Successfully opened config file\n"); |
|
struct utun_config *config = parse_config_internal(fp, filename); |
|
fclose(fp); |
|
return config; |
|
} |
|
|
|
void free_config(struct utun_config *config) { |
|
if (!config) return; |
|
|
|
// Free servers |
|
struct CFG_SERVER *server = config->servers; |
|
while (server) { |
|
struct CFG_SERVER *next = server->next; |
|
free(server); |
|
server = next; |
|
} |
|
|
|
// Free clients and their links |
|
struct CFG_CLIENT *client = config->clients; |
|
while (client) { |
|
struct CFG_CLIENT *next = client->next; |
|
free_cfg_client_links(client->links); |
|
free(client); |
|
client = next; |
|
} |
|
|
|
// Free route entries |
|
free_route_entries(config->allowed_subnets); |
|
free_route_entries(config->my_subnets); |
|
|
|
free(config); |
|
} |
|
|
|
static const char* ip_to_string(const struct IP *ip, char *buffer, size_t buffer_size) { |
|
if (ip->family == AF_INET) { |
|
inet_ntop(AF_INET, &ip->addr.v4, buffer, buffer_size); |
|
} else if (ip->family == AF_INET6) { |
|
inet_ntop(AF_INET6, &ip->addr.v6, buffer, buffer_size); |
|
} else { |
|
snprintf(buffer, buffer_size, "invalid"); |
|
} |
|
return buffer; |
|
} |
|
|
|
void print_config(const struct utun_config *cfg) { |
|
if (!cfg) return; |
|
|
|
const struct global_config *g = &cfg->global; |
|
char ip_buffer[64]; |
|
|
|
printf("Global:\n"); |
|
printf(" Private key: %s\n", g->my_private_key_hex); |
|
printf(" Public key: %s\n", g->my_public_key_hex); |
|
printf(" Node ID: %llx\n", (unsigned long long)g->my_node_id); |
|
printf(" TUN interface: %s\n", g->tun_ifname[0] ? g->tun_ifname : "auto"); |
|
printf(" TUN IP: %s\n", ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer))); |
|
printf(" MTU: %d\n", g->mtu); |
|
printf(" Net debug: %d\n", g->net_debug); |
|
|
|
printf("\nServers:\n"); |
|
struct CFG_SERVER *s = cfg->servers; |
|
while (s) { |
|
char addr_buffer[64]; |
|
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip; |
|
inet_ntop(AF_INET, &sin->sin_addr, addr_buffer, sizeof(addr_buffer)); |
|
printf(" %s: %s:%d (mark=%d, netif=%u, type=%u)\n", |
|
s->name, addr_buffer, ntohs(sin->sin_port), |
|
s->so_mark, s->netif_index, s->type); |
|
s = s->next; |
|
} |
|
|
|
printf("\nClients:\n"); |
|
struct CFG_CLIENT *c = cfg->clients; |
|
while (c) { |
|
printf(" %s:\n", c->name); |
|
struct CFG_CLIENT_LINK *link = c->links; |
|
int link_num = 1; |
|
while (link) { |
|
char addr_buffer[64]; |
|
struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr; |
|
inet_ntop(AF_INET, &sin->sin_addr, addr_buffer, sizeof(addr_buffer)); |
|
printf(" Link %d: %s:%d (via %s)\n", link_num++, addr_buffer, ntohs(sin->sin_port), link->local_srv->name); |
|
link = link->next; |
|
} |
|
printf(" Peer key: %s\n", c->peer_public_key_hex); |
|
printf(" Keepalive: %d\n", c->keepalive); |
|
c = c->next; |
|
} |
|
|
|
printf("\nAllowed Subnets:\n"); |
|
struct CFG_ROUTE_ENTRY *allowed = cfg->allowed_subnets; |
|
while (allowed) { |
|
printf(" %s/%d\n", ip_to_string(&allowed->ip, ip_buffer, sizeof(ip_buffer)), allowed->netmask); |
|
allowed = allowed->next; |
|
} |
|
|
|
printf("\nMy Subnets:\n"); |
|
struct CFG_ROUTE_ENTRY *my = cfg->my_subnets; |
|
while (my) { |
|
printf(" %s/%d\n", ip_to_string(&my->ip, ip_buffer, sizeof(ip_buffer)), my->netmask); |
|
my = my->next; |
|
} |
|
} |
|
|
|
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) { |
|
// Minimal implementation: just append to file |
|
FILE *fp = fopen(filename, "a"); |
|
if (!fp) return -1; |
|
|
|
fprintf(fp, "\n[global]\n"); |
|
fprintf(fp, "my_private_key=%s\n", priv_key); |
|
fprintf(fp, "my_public_key=%s\n", pub_key); |
|
|
|
fclose(fp); |
|
return 0; |
|
} |