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.
 
 
 
 
 
 

1066 lines
38 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 "../lib/platform_compat.h"
#include <errno.h>
#ifndef _WIN32
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#endif
#include "../lib/mem.h"
#define MAX_LINE_LEN 1024
#define INITIAL_ARRAY_CAPACITY 8
/* Forward declaration for new functions */
static int assign_string(char *dest, size_t dest_size, const char *src);
static int parse_firewall_rule(const char *rule_str, struct global_config *global);
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) {
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1;
for (size_t i = 0; i < binary_len; i++) {
unsigned int byte;
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1;
binary[i] = (uint8_t)byte;
}
return 0;
}
typedef enum {
SECTION_UNKNOWN,
SECTION_GLOBAL,
SECTION_SERVER,
SECTION_CLIENT,
SECTION_ROUTING,
SECTION_DEBUG,
SECTION_FIREWALL,
SECTION_CONTROL,
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY,
SECTION_REMOTE_PROXY
} 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);
char *comment = strchr(value, '#');
if (comment) *comment = '\0';
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;
char clean_addr[MAX_ADDR_LEN];
const char *a = addr_str;
if (addr_str[0] == '[') {
size_t len = strlen(addr_str);
if (len > 2 && addr_str[len - 1] == ']') {
if (len - 2 >= sizeof(clean_addr)) return -1;
memcpy(clean_addr, addr_str + 1, len - 2);
clean_addr[len - 2] = '\0';
a = clean_addr;
}
}
struct addrinfo *result;
if (getaddrinfo(a, 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;
#ifdef _WIN32
// Windows doesn't have if_nametoindex, return 0 to indicate no specific interface
(void)ifname;
return 0;
#else
return if_nametoindex(ifname);
#endif
}
static struct CFG_CLIENT_LINK* create_client_link(struct CFG_SERVER* local_srv, const char *remote_addr) {
struct CFG_CLIENT_LINK *link = u_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) {
u_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;
u_free(links);
links = next;
}
}
static struct CFG_ROUTE_ENTRY* create_route_entry(const char *subnet_str) {
struct CFG_ROUTE_ENTRY *entry = u_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) {
u_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;
u_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 int parse_firewall_rule(const char *rule_str, struct global_config *global) {
if (!rule_str || !global) return -1;
char rule_copy[64];
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
rule_copy[sizeof(rule_copy) - 1] = '\0';
trim(rule_copy);
if (strcasecmp(rule_copy, "all") == 0) {
global->firewall_bypass_all = 1;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall: bypass all enabled");
return 0;
}
// Parse IP:port or IP
char *port_str = strchr(rule_copy, ':');
uint16_t port = 0;
if (port_str) {
*port_str = '\0';
port = (uint16_t)atoi(port_str + 1);
}
// Parse IP to uint32_t (host order)
struct in_addr addr;
if (inet_pton(AF_INET, rule_copy, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_firewall_rule: invalid IP: %s", rule_str);
return -1;
}
uint32_t ip = ntohl(addr.s_addr); // convert to host order for sorting
// Reallocate array if needed
if ((global->firewall_rule_count % INITIAL_ARRAY_CAPACITY) == 0) {
int new_cap = global->firewall_rule_count + INITIAL_ARRAY_CAPACITY;
struct CFG_FIREWALL_RULE *new_rules = u_realloc(global->firewall_rules,
new_cap * sizeof(struct CFG_FIREWALL_RULE));
if (!new_rules) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc firewall rules");
return -1;
}
global->firewall_rules = new_rules;
}
struct CFG_FIREWALL_RULE *rule = &global->firewall_rules[global->firewall_rule_count];
rule->ip = ip;
rule->port = port;
rule->bypass = 0;
global->firewall_rule_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Added firewall rule: %s:%d", rule_str, port);
return 0;
}
static int parse_control_allow(const char *rule_str, struct global_config *global) {
if (!rule_str || !global) return -1;
char rule_copy[64];
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
rule_copy[sizeof(rule_copy) - 1] = '\0';
trim(rule_copy);
uint8_t cidr = 32;
char *slash = strchr(rule_copy, '/');
if (slash) {
*slash = '\0';
cidr = (uint8_t)atoi(slash + 1);
if (cidr > 32) cidr = 32;
}
struct in_addr addr;
if (inet_pton(AF_INET, rule_copy, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str);
return -1;
}
uint32_t ip = ntohl(addr.s_addr);
uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr));
if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) {
int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY;
struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows,
new_cap * sizeof(struct CFG_CONTROL_ALLOW));
if (!new_allows) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc control allows");
return -1;
}
global->control_allows = new_allows;
}
struct CFG_CONTROL_ALLOW *rule = &global->control_allows[global->control_allow_count];
rule->network = ip & mask;
rule->netmask = mask;
global->control_allow_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Added control_allow: %s/%u", rule_str, (unsigned)cidr);
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_node_name") == 0) {
return assign_string(global->name, sizeof(global->name), value);
}
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, "keepalive_timeout") == 0) {
global->keepalive_timeout = atoi(value);
return 0;
}
if (strcmp(key, "keepalive_interval") == 0) {
global->keepalive_interval = atoi(value);
return 0;
}
if (strcmp(key, "inflight_min_bytes") == 0) {
global->inflight_min_bytes = atoi(value);
return 0;
}
if (strcmp(key, "inflight_max_bytes") == 0) {
global->inflight_max_bytes = atoi(value);
return 0;
}
if (strcmp(key, "debug_level") == 0) {
return assign_string(global->debug_level, sizeof(global->debug_level), value);
}
if (strcmp(key, "log_file") == 0) {
return assign_string(global->log_file, sizeof(global->log_file), value);
}
/* debug_categories key is deprecated - use [debug] section instead */
if (strcmp(key, "enable_timestamp") == 0) {
global->enable_timestamp = atoi(value);
return 0;
}
if (strcmp(key, "enable_function_names") == 0) {
global->enable_function_names = atoi(value);
return 0;
}
if (strcmp(key, "enable_file_lines") == 0) {
global->enable_file_lines = atoi(value);
return 0;
}
if (strcmp(key, "enable_colors") == 0) {
global->enable_colors = atoi(value);
return 0;
}
if (strcmp(key, "tun_test_mode") == 0) {
global->tun_test_mode = atoi(value);
return 0;
}
return 0;
}
static int parse_control(const char *key, const char *value, struct global_config *global) {
if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) {
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
return 0;
}
if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) {
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
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, "allow") == 0 || strcmp(key, "control_allow") == 0) {
return parse_control_allow(value, global);
}
return 0;
}
static int parse_tcp_proxy(const char *key, const char *value, struct global_config *global) {
if (strcmp(key, "enabled") == 0) {
global->tcp_proxy_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "tun_name") == 0) {
strncpy(global->tcp_proxy_tun_name, value, sizeof(global->tcp_proxy_tun_name) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
strncpy(global->tcp_proxy_tun_ip, value, sizeof(global->tcp_proxy_tun_ip) - 1);
return 0;
}
if (strcmp(key, "mtu") == 0) {
global->tcp_proxy_mtu = atoi(value);
return 0;
}
if (strcmp(key, "eim_timeout") == 0) {
global->tcp_proxy_eim_timeout = atoi(value);
return 0;
}
if (strcmp(key, "via_node") == 0) {
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->tcp_proxy_mapping_count >= MAX_TCP_PROXY_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy forward rules (max %d)", MAX_TCP_PROXY_MAPPINGS);
return -1;
}
char buf[256];
strncpy(buf, value, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
trim(buf);
char* local_port_str = strtok(buf, " ");
char* arrow = strtok(NULL, " ");
char* remote_str = strtok(NULL, "");
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward format: %s (expected: port -> ip:port)", value);
return -1;
}
int local_port = atoi(local_port_str);
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward local port: %s", local_port_str); return -1; }
char* remote_ip_str = strtok(remote_str, ":");
char* remote_port_str = strtok(NULL, "");
if (!remote_ip_str || !remote_port_str) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote: %s (expected ip:port)", remote_str);
return -1;
}
int remote_port = atoi(remote_port_str);
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote port: %s", remote_port_str); return -1; }
int idx = global->tcp_proxy_mapping_count;
global->tcp_proxy_mappings[idx].local_port = (uint16_t)local_port;
strncpy(global->tcp_proxy_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_mappings[idx].remote_ip) - 1);
global->tcp_proxy_mappings[idx].remote_port = (uint16_t)remote_port;
global->tcp_proxy_mapping_count++;
return 0;
}
return 0;
}
static int parse_nat(const char *key, const char *value, struct global_config *global) {
if (strcmp(key, "tun_ifname") == 0) {
strncpy(global->nat_tun_ifname, value, sizeof(global->nat_tun_ifname) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
uint8_t netmask;
parse_ip_with_netmask(value, &global->nat_tun_ip, &netmask);
return 0;
}
if (strcmp(key, "nat_via") == 0) {
global->nat_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "port_start") == 0) {
global->nat_port_start = (uint16_t)atoi(value);
return 0;
}
if (strcmp(key, "port_end") == 0) {
global->nat_port_end = (uint16_t)atoi(value);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->nat_forward_count >= MAX_NAT_FORWARDS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many NAT forward rules (max %d)", MAX_NAT_FORWARDS);
return -1;
}
// Format: proto:internal_ip:internal_port:external_port
// Example: tcp:192.168.1.100:8080:8080
char buf[128];
strncpy(buf, value, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
trim(buf);
char* proto_str = strtok(buf, ":");
char* ip_str = strtok(NULL, ":");
char* int_port = strtok(NULL, ":");
char* ext_port = strtok(NULL, ":");
if (!proto_str || !ip_str || !int_port || !ext_port) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward format: %s (expected proto:ip:internal_port:external_port)", value);
return -1;
}
uint8_t proto;
if (strcasecmp(proto_str, "tcp") == 0) proto = IPPROTO_TCP;
else if (strcasecmp(proto_str, "udp") == 0) proto = IPPROTO_UDP;
else {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward proto: %s (tcp/udp)", proto_str);
return -1;
}
struct in_addr addr;
if (inet_pton(AF_INET, ip_str, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward IP: %s", ip_str);
return -1;
}
int idx = global->nat_forward_count;
global->nat_forwards[idx].proto = proto;
global->nat_forwards[idx].internal_ip_host = ntohl(addr.s_addr);
global->nat_forwards[idx].internal_port_net = htons((uint16_t)atoi(int_port));
global->nat_forwards[idx].external_port = (uint16_t)atoi(ext_port);
global->nat_forward_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "NAT forward: %s %s:%s -> :%s",
proto_str, ip_str, int_port, ext_port);
return 0;
}
return 0;
}
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) {
if (strcmp(key, "addr") == 0) {
if (strncmp(value, "temporary_ipv6:", 15) == 0) {
srv->ipv6_mode = CFG_IPV6_MODE_TEMPORARY;
const char* port_str = value + 15;
if (!port_str[0]) return -1;
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
memset(&srv->ip, 0, sizeof(srv->ip));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons((uint16_t)atoi(port_str));
return 0;
}
if (strncmp(value, "permanent_ipv6:", 15) == 0) {
srv->ipv6_mode = CFG_IPV6_MODE_PERMANENT;
const char* port_str = value + 15;
if (!port_str[0]) return -1;
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
memset(&srv->ip, 0, sizeof(srv->ip));
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons((uint16_t)atoi(port_str));
return 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, "fib") == 0) {
srv->fib = 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;
}
if (strcmp(key, "mtu") == 0) {
srv->mtu = atoi(value);
return 0;
}
if (strcmp(key, "only_local") == 0) {
srv->only_local = atoi(value) ? 1 : 0;
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;
if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG;
if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL;
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
if (strcasecmp(section, "tcp_proxy") == 0) return SECTION_TCP_PROXY;
if (strcasecmp(section, "remote_proxy") == 0) return SECTION_REMOTE_PROXY;
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 = u_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;
}
// Set default values
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.inflight_min_bytes = 2000; // Default 2KB
cfg->global.inflight_max_bytes = 100000; // Default 100KB
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;
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 = u_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);
cur_server->fib = -1;
} else if (cur_section == SECTION_CLIENT) {
cur_client = u_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) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key=value format", filename, line_num);
continue;
}
switch (cur_section) {
case SECTION_GLOBAL:
if (parse_global(key, value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid global key '%s'", filename, line_num, key);
}
break;
case SECTION_SERVER:
if (cur_server && parse_server(key, value, cur_server) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%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) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid client key '%s'", filename, line_num, key);
}
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);
}
break;
case SECTION_DEBUG:
// Format: category=level (e.g., etcp=debug, crypto=info)
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) {
if (parse_firewall_rule(value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid firewall rule: %s", filename, line_num, value);
}
}
break;
case SECTION_CONTROL:
if (parse_control(key, value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid control key '%s'", filename, line_num, key);
}
break;
case SECTION_ALLOWED_KEYS:
if (strcmp(key, "allow_all") == 0) {
cfg->global.allowed_keys_allow_all = atoi(value) ? 1 : 0;
} else if (strcmp(key, "key") == 0) {
struct CFG_ALLOWED_KEY *new_key = u_calloc(1, sizeof(struct CFG_ALLOWED_KEY));
if (!new_key) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate allowed key");
} else if (strlen(value) != SC_PUBKEY_SIZE * 2) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key length %zu, expected %d", filename, line_num, strlen(value), SC_PUBKEY_SIZE * 2);
u_free(new_key);
} else if (hex_to_binary(value, new_key->key_bin, SC_PUBKEY_SIZE) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid hex key format", filename, line_num);
u_free(new_key);
} else {
new_key->next = cfg->global.allowed_keys;
cfg->global.allowed_keys = new_key;
cfg->global.allowed_keys_count++;
}
}
break;
case SECTION_NAT:
cfg->global.nat_enabled = 1;
if (parse_nat(key, value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid NAT key '%s'", filename, line_num, key);
}
break;
case SECTION_TCP_PROXY:
cfg->global.tcp_proxy_enabled = 1;
if (parse_tcp_proxy(key, value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid tcp_proxy key '%s'", filename, line_num, key);
}
break;
case SECTION_REMOTE_PROXY:
cfg->global.remote_proxy_enabled = 1;
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%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) u_free(cur_server);
if (cur_client) {
free_cfg_client_links(cur_client->links);
u_free(cur_client);
}
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");
if (!fp) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to open config file: %s (errno=%d)", filename, errno);
return NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Successfully opened config file");
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;
u_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);
u_free(client);
client = next;
}
// Free route entries
free_route_entries(config->route_subnets);
free_route_entries(config->my_subnets);
// Free firewall rules
u_free(config->global.firewall_rules);
// Free control allow rules (array allocated with realloc)
u_free(config->global.control_allows);
// Free allowed keys
struct CFG_ALLOWED_KEY *ak = config->global.allowed_keys;
while (ak) {
struct CFG_ALLOWED_KEY *next = ak->next;
u_free(ak);
ak = next;
}
u_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];
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d",
g->my_private_key_hex, g->my_public_key_hex,
(unsigned long long)g->my_node_id,
g->tun_ifname[0] ? g->tun_ifname : "auto",
ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)),
g->mtu, g->keepalive_timeout, g->tun_test_mode);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
struct CFG_SERVER *s = cfg->servers;
while (s) {
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d)",
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
s->so_mark, s->netif_index, s->type, s->mtu);
s = s->next;
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Clients:");
struct CFG_CLIENT *c = cfg->clients;
while (c) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: peer_key=%s, keepalive=%d", c->name, c->peer_public_key_hex, c->keepalive);
struct CFG_CLIENT_LINK *link = c->links;
while (link) {
struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s:%d (via %s)",
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), link->local_srv->name);
link = link->next;
}
c = c->next;
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Route Subnets:");
struct CFG_ROUTE_ENTRY *allowed = cfg->route_subnets;
while (allowed) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s/%d", ip_to_string(&allowed->ip, ip_buffer, sizeof(ip_buffer)), allowed->netmask);
allowed = allowed->next;
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "My Subnets:");
struct CFG_ROUTE_ENTRY *my = cfg->my_subnets;
while (my) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s/%d", ip_to_string(&my->ip, ip_buffer, sizeof(ip_buffer)), my->netmask);
my = my->next;
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall:");
if (cfg->global.firewall_bypass_all) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " bypass_all=1");
} else if (cfg->global.firewall_rule_count > 0) {
for (int i = 0; i < cfg->global.firewall_rule_count; i++) {
struct CFG_FIREWALL_RULE *r = &cfg->global.firewall_rules[i];
char ip_str[16];
struct in_addr ia = { .s_addr = htonl(r->ip) };
inet_ntop(AF_INET, &ia, ip_str, sizeof(ip_str));
if (r->port == 0) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s", ip_str);
} else {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s:%d", ip_str, r->port);
}
}
} else {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules");
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Control allows: %d rules (default deny all if 0)", g->control_allow_count);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Allowed keys: allow_all=%d, count=%d", g->allowed_keys_allow_all, g->allowed_keys_count);
if (!g->allowed_keys_allow_all && g->allowed_keys_count > 0) {
struct CFG_ALLOWED_KEY *ak = g->allowed_keys;
while (ak) {
char key_hex[SC_PUBKEY_SIZE * 2 + 1];
for (int i = 0; i < SC_PUBKEY_SIZE; i++) {
snprintf(key_hex + i * 2, sizeof(key_hex) - i * 2, "%02x", ak->key_bin[i]);
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " key=%s", key_hex);
ak = ak->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;
}